Drupal 用户实现单一登陆

单一登陆什么意思呢?也就是一个账号同时只能在一个地方登陆. 实现方式是当用户登陆成功以后删除当前账号在其它的地方的登陆session

/**
 * Implements hook_user_login().
 * 
 * User logged in callback.
 */
function mymodule_user_login(&$edit, $account) {
  // 用户单一登陆实现.
  db_query('DELETE FROM `sessions` WHERE uid=:uid AND sid!=:sid', array(
    ':uid' => $account->uid,
    ':sid' => session_name()
  ));
}

Drupal 用户登陆

看之前请先看 Cookie与Session的工作原理

当运行session_start()时,会创建一个PHPSISSID的Cookie, 而且这个id也是随机生成
但是drupal中的这里有点不大一样. 这个id生成是由drupal_random_key()函数生成. 参考:

session_id(drupal_random_key());

当用户正常登陆以后会根据用户的uid创建一个session. 请检查sessions表
屏幕快照 2015-01-09 16.21.02

当用户第二次打开网站的时候会去检查这个cookie是否存在,如果存在验证这个cookie然后初始化当用户. 如果不存在则重新登陆.
参考函数:
drupal_session_initialize()

Drupal中允许一个用户在多个地方登陆,如果想只能一个地方登陆可以这样:

/**
 * Implements hook_user_login().
 *
 * User logged in callback.
 */
function mymodule_user_login(&$edit, $account) {
  // 用户单一登陆实现.
  db_query('DELETE FROM `sessions` WHERE uid=:uid AND sid!=:sid', array(
    ':uid' => $account->uid,
    ':sid' => session_name()
  ));
}

在Drupal中如果中途改变了全局变量$user, 登陆用户将会切换 Why?

global $user;
$user = user_load(1);

drupal_page_footet()是默认html驱动器(drupal_deliver_html_page)最好执行的函数

global $user;
module_invoke_all('exit');
// Commit the user session, if needed.
drupal_session_commit();

在这个函数将会依次执行drupal_session_commit -> session_write_close -> _drupal_session_write
在_drupal_session_write中更新Session信息

      db_merge('sessions')
        ->key($key)
        ->fields($fields)
        ->execute();
    }

大巧若拙,大辩若讷

最有智慧的人,真正有本事的人,虽然有才华学识,但平时像个呆子,不自作聪明;虽然能言善辩,但好像不会讲话一样。无论是初涉世事还是位居高官,无论是做大事还是一般人际关系,锋芒不可毕露。有了才华固然很好,但在合适的时机运用才华而不被或少被人忌,避免功高盖主,才算是更大的才华,这种才华对社会、对人对己才有真正的用处。

Drupal Fatal error: Class name must be a valid object or a string in includes/common.inc on line 7680

如果出现Entity这种错误算是比较严重的了。这里只说如何调试
view

/**
 * Get the entity controller class for an entity type.
 */
function entity_get_controller($entity_type) {
  $controllers = &drupal_static(__FUNCTION__, array());
  if (!isset($controllers[$entity_type])) {
    $type_info = entity_get_info($entity_type);
    $class = $type_info['controller class'];
    $controllers[$entity_type] = new $class($entity_type);
  }
  return $controllers[$entity_type];
}

TRY

$class = isset($type_info['controller class']) ? $type_info['controller class'] : 'DrupalDefaultEntityController';

TO

/**
 * Get the entity controller class for an entity type.
 */
function entity_get_controller($entity_type) {
  $controllers = &drupal_static(__FUNCTION__, array());
  if (!isset($controllers[$entity_type])) {
    $type_info = entity_get_info($entity_type);
    $class = $type_info['controller class'];

    // 增加一个debug调试,看看问题从哪里来.
    if(gettype($class) == 'string' || gettype($class) == 'object') {
      $controllers[$entity_type] = new $class($entity_type);
    } else  {
      print_r(debug_backtrace());
    }
    $controllers[$entity_type] = new $class($entity_type);
  }
  return $controllers[$entity_type];
}

PHP文件大小格式化与反格式化

此代码来自drupal


define('DRUPAL_KILOBYTE', 1024);

/**
 * Parses a given byte count.
 *
 * @param $size
 *   A size expressed as a number of bytes with optional SI or IEC binary unit
 *   prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes).
 *
 * @return
 *   An integer representation of the size in bytes.
 */
function parse_size($size) {
  $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
  $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
  if ($unit) {
    // Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
    return round($size * pow(DRUPAL_KILOBYTE, stripos('bkmgtpezy', $unit[0])));
  }
  else {
    return round($size);
  }
}

/**
 * Generates a string representation for the given byte count.
 *
 * @param $size
 *   A size in bytes.
 * @param $langcode
 *   Optional language code to translate to a language other than what is used
 *   to display the page.
 *
 * @return
 *   A translated string representation of the size.
 */
function format_size($size) {
  if ($size < DRUPAL_KILOBYTE) {
    return str_replace('@count bytes', '@count', $size);
  }
  else {
    $size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes.
    $units = array(
      '@size KB',
      '@size MB',
      '@size GB',
      '@size TB',
      '@size PB',
      '@size EB',
      '@size ZB',
      '@size YB',
    );
    foreach ($units as $unit) {
      if (round($size, 2) >= DRUPAL_KILOBYTE) {
        $size = $size / DRUPAL_KILOBYTE;
      }
      else {
        break;
      }
    }
    return str_replace('@size', round($size, 2), $unit);
  }
}

var_dump(format_size(2003330));

输入: 1.91KB

PHP filter 验证

以前要验证邮箱,ip, url等常用验证通常都是写了一个正则表达式,今天看到在drupal的includes/common.inc中有这么一个函数filter_var一查竟然是php函数,我以前竟然不知道。而且是从php5.2就开始提供了, 用法如此简单.

验证ip:

// ipv4, 最后的参数可以不用。默认就是ipv4.
filter_var('192.168.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);

// ipv6.
filter_var('fe80::6e40:8ff:fe99:dee2', FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);

// ipv4 或 ipv6. 只要是后面参数有多个都可以用这种表达方式.
filter_var('192.168.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6);

验证邮箱

filter_var('zhouitpro@gmail.com', FILTER_VALIDATE_EMAIL);

URL

filter_var('http://www.baidu.com', FILTER_VALIDATE_URL);

常用的就以上几个,当然还有mac验证,浮点数和正正常难证等.
参考:http://php.net/manual/zh/function.filter-var.php
参数参考: http://php.net/manual/zh/filter.filters.validate.php

数组批量验证:

$data = array(
  'id' => 10085,
  'url' => 'http://www.baidu.com',
  'myip' => '192.168.0.22',
  'myfl' => 0.23
);
$filter = array(
  'id' => FILTER_VALIDATE_INT,
  'url' => FILTER_VALIDATE_URL,
  'myip' => FILTER_VALIDATE_IP | FILTER_FLAG_IPV6,
  'myfl' => FILTER_VALIDATE_FLOAT
);
$result = filter_var_array($data, $filter);


// 验证邮箱地址:
$emails = array(
  'zhouitpro@gmail.com',
  '244705779@qq.com',
  'helloworld',
);

$filters = filter_var_array($emails, FILTER_VALIDATE_EMAIL);
if(in_array(false, $filters)) {
  var_dump('Email格式不正确');
}

// 验证get参数
$filter = array(
  'id' => FILTER_VALIDATE_INT,
  'callback' => FILTER_VALIDATE_URL
);
$filter = filter_var_array($_GET, $filter);
if(in_array(null, $filter) OR in_array(false, $filter)) {
  echo '参数不正确';
  exit();
}

更多请参考:http://php.net/manual/zh/function.filter-var-array.php

验证$_GET, $_POST, $_SERVER, $_SESSION, $_EVT, $_COOKIE数据格式

/**
 * $_GET => INPUT_GET
 * $_POST = > INPUT_POST
 * $_SERVER = INPUT_SERVER
 * $_EVT = INPUT_EVT
 * $_COOKE = INPUT_COOKIE
 * $_SESSION = INPUT_SESSION
 */
var_dump(filter_input(INPUT_GET, 'test', FILTER_VALIDATE_IP));

// 以前写法.
$get = isset($_GET['test']) ? $_GET['test'] : null;

// 现在可以这样写. 如果test不存在返回null.并不会报错.
$get = filter_input(INPUT_GET, 'test');
/**
 * Filter email.
 */
$string = 'zhouitpro@gmail.com';
var_dump(filter_var($string, FILTER_SANITIZE_EMAIL));


/**
 * Filter string.
 */
$string = '!!';
var_dump(filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_ENCODE_LOW));

/**
 * 3rd component
 *
 * aura/filter
 * respect/validation
 * symfony/validator
 */

参考:http://php.net/manual/zh/function.filter-input.php

Mac 安装Xdebug

xdebug依然是php调试神器,怎么能没有呢。。

使用pecl安装

$sudo pecl install xdebug

不过这样会遇到一个错误.

downloading xdebug-2.2.3.tgz ...
Starting to download xdebug-2.2.3.tgz (250,543 bytes)
.....................................................done: 250,543 bytes
66 source files, building
running: phpize
grep: /usr/include/php/main/php.h: No such file or directory
grep: /usr/include/php/Zend/zend_modules.h: No such file or directory
grep: /usr/include/php/Zend/zend_extensions.h: No such file or directory
Configuring for:
PHP Api Version:
Zend Module Api No:
Zend Extension Api No:
Cannot find autoconf. Please check your autoconf installation and the
$PHP_AUTOCONF environment variable. Then, rerun this script.

ERROR: `phpize' failed

做一些修复工作

$sudo sh -c 'echo zend_extension=$(find /usr/lib/php/extensions -name "xdebug.so") >> $(php -qr "echo php_ini_loaded_file();") && apachectl restart'

HTML5 postMessage

html 5 postmessage 方法可实现不同窗体间互相通信. 可以发送对象、数组、字符串等. 可以实现多窗口变量对象共享等功能.

发送信息

// 发送信息到子窗口.
var iframe = $("myiframe");
iframe.get(0).contentWindow.postMessage('message(string,object,array...)', 'http://yourdomain.com');

// 发送信息到父窗口
parent.postmessage('message(string,object,array...)', 'http://yourdomain.com');

接收信息

window.onmessage = function(evt){
 var data = evt.data;
}

以下是一个完整的iframe发送信息的案列.
/**index.php**/



    
     
     
    
  
      

/**iframe.php*/



    
        
        
    
    
        

html5 post message

Drupal hook_update_N工作原理

Drupal有一个hook叫hook_update_N(), 比如以下

function mymodule_update_1() {
}

function mymodule_update_100() {
}

function mymodule_update_7000() {
}

那是如何去执行指定的函数呢,并且后面这个数字是完全没有规律的.
打开数据库system表的schema_version字段。这个字段是保存每一个模块最后更新版本号。在更新的时候会执行大于schema_version保存的这个数字的所有函数(针对单个的模块)并且执行。

那drupal如何是如何找出来大于schema_version的所有函数呢?

首先我们以更新views模块为例子, 比如views模块中的schema_version字段当前是7000, 那么它将会执行以下几个更新函数

function views_update_6009() {}
function views_update_7003() {}
function views_update_7020() {}
// 遍历所有用户自定义的函数.
$functions = get_defined_functions();
$module = 'views';

$updates = array();
// 遍历所有以_数字结尾的函数
foreach(preg_grep('/_\d+$/', $functions['user']) as $function) {
  if (preg_match('/^(?P.+)_update_(?P\d+)$/', $function, $matches)) {
    $updates[$matches['module']][] = $matches['version'];
  }
}

var_export($updates);
// 从而可以获取到结果为
array ( 'views' => array ( 0 => '6009', 1 => '7003', 2 => '7020', ), )

// 现在就可以将获取出来的值与数据库的schema_version进行比较了.大于就执行
foreach($updates['views'] as $update) {
  if($update > 7000) {
    $function = "views_update_{$update}";
    $function();
     // ..... 执行更新代码
  }
}

参考函数: https://api.drupal.org/api/drupal/includes%21install.inc/function/drupal_get_schema_versions/7
https://api.drupal.org/api/drupal/includes%21update.inc/function/update_get_update_list/7