DiscuzX根据版块限制附件大小

最近公司网站贵州都市网又需要这么一个功能. 之前程序还是基于Discuz7的时候做过一次, 是通过修改upload.swf传递fid实现的.

现在升级到了DiscuzX1.5因为程序自己就已经传递了fid所以也就省去了修改upload.swf这一步(之前修改upload.swf把我折腾的够呛).

不扯废话上代码了..

打开/source/module/misc/misc_swfupload.php 在echo之前加入如下代码

    if(in_array($_GET['fid'], array(126))) { // 版块Fid
        $_G['group']['maxattachsize'] = 2097152; // 大小
    }

继续打开/source/class/class_forumupload.php 在大概56,7行的样子, 也就是判断文件大小之前也加上上面这段代码.
保存, 搞定, 收工.

我的Vim

之前我一直使用Editplus, 向别人推荐时也一律推荐Edplus.

但现在如果有人要我推荐开发工具, 我会毫不犹豫的向对方推荐Vim. 一切只因Vim太强悍.

自从用了Vim, 眼不疼了, 手不疼了, 一口气写N多代码不费力了. 哈哈.

秀一下我的Vim

配色: lucius, 字体:Consolas, 字体大小14

目录树插件: NERDTree

 

ZendCodeAnalyzer

 

其他插件: ZenCoding, LoadTemplate,

点这里下载我的Vim配置.

为Discuz! X1.5增加后台管理菜单

这里已添加一个顶部菜单”美食”为例

首先打开admin.php文件数组变量$admincp_actions_normal里加入一项

…..省略

$admincp_actions_normal = array('food', 'index', 'setting', 'members', 'profilefields', 'admingroup'......省略

之后新建立目录/source/admincp/menu/  扩展菜单目录

再之后新建文件/source/admincp/menu/menu_food.php  菜单配置

写入文件内容

< ?php

if(!defined('IN_DISCUZ') || !defined('IN_ADMINCP')) {
	exit('Access Denied');
}

$topmenu['food'] = 'food_basic_setting';
food在语言包中对应header_food
food_basic_setting对应URL admin.php?action=food&operation=basic&do=setting

$menu['food']    = array(
    // 这里的下标和语言包中的对应 => '点击菜单转到的URL格式为(action_operation_do比如下面这个就是admin.php?action=food&operation=basic&do=information)',
    array('menu_food_information', 'food_basic_information'),
);

新建一个语言包文件/source/language/lang_admincp_food.php 菜单语音包. 如果你愿意也可以直接更改/source/language/lang_admincp_menu.php. 这个是discuz自带的语言包. 我认为还是分开好.

文件内容

< ?php
if(!defined('IN_DISCUZ') || !defined('IN_ADMINCP')) {
	exit('Access Denied');
}
$extend_lang = array(
     'header_food' => '美食',
     'menu_food_setting' => '设置',
     'menu_food_information' => '程序信息',
);

保存所有文件, 刷新浏览器. 搞定, 收工.

如果你想了解的更清楚请查看以下几个文件:
/admin.php
/source/admincp/admincp_main.php
/source/admincp/admincp_menu.php
/source/language/lang_admincp.php

Discuz!X 1.5 实现群组与版块自动关联

最近公司网站 贵州都市网 准备升级.

在PHPWind8 和 Discuz!X 1.5之间纠结, 主要原因就是PHPWind8群组可以与论坛的板块关联而Discuz!X 1.5不能.

但纠结过后还是选择了Discuz!X 1.5, 群组与板块关联我们自己实现.

下面分享下我做的群组板块关联的实现.

打开/config/目录新建一个文件config_groupjoin.php

文件内容为

< ?php
$groupjoin = array(
    36 => 2
    // 群组id => 板块ID
);

打开/source/include/post/post_thread.php在大概520行左右找到

if($isgroup) {
    DB::query("UPDATE ".DB::table('forum_groupuser')." SET threads=threads+1, lastupdate='".TIMESTAMP."' WHERE uid='$_G[uid]' AND fid='$_G[fid]'");
}

改为

if($isgroup) {
    DB::query("UPDATE ".DB::table('forum_groupuser')." SET threads=threads+1, lastupdate='".TIMESTAMP."' WHERE uid='$_G[uid]' AND fid='$_G[fid]'");
    include DISCUZ_ROOT . './config/config_groupjoin.php';
    if (isset($groupjoin[$_G['fid']])) {
        $moveto = $groupjoin[$_G['fid']];
        DB::query("INSERT INTO ".DB::table('forum_thread')." (fid, posttableid, readperm, price, typeid, author, authorid, subject, dateline, lastpost, lastposter, displayorder, digest, special, attachment, moderated, replies, status, isgroup, closed)
VALUES ('$moveto', '$posttableid', '$readperm', '$price', '$typeid', '$author', '$_G[uid]', '$subject', '$_G[timestamp]', '$_G[timestamp]', '$author', '$displayorder', '$digest', '$special', '$attachment', '$moderated', '1', '$thread[status]', '1', '$tid')");
    }
}

如果你的板块和群组能发表的主题类型一样, 则到此论坛与群组算是关联上了. 但人如果不同, 比如群组能发投票而板块不行, 那就请继续看下面.

打开/source/include/post/post_thread.php在大概520行左右找到

if($isgroup) {
    DB::query("UPDATE ".DB::table('forum_groupuser')." SET threads=threads+1, lastupdate='".TIMESTAMP."' WHERE uid='$_G[uid]' AND fid='$_G[fid]'");
}

修改为

if($isgroup) {
    DB::query("UPDATE ".DB::table('forum_groupuser')." SET threads=threads+1, lastupdate='".TIMESTAMP."' WHERE uid='$_G[uid]' AND fid='$_G[fid]'");
    include DISCUZ_ROOT . './config/config_groupjoin.php';
    if (isset($groupjoin[$_G['fid']])) {
        $moveto = $groupjoin[$_G['fid']];

        // 关联板块信息
        $toforum = DB::fetch_first("SELECT f.fid, f.name, f.modnewposts, f.allowpostspecial, ff.threadplugin FROM ".DB::table('forum_forum')." f LEFT JOIN ".DB::table('forum_forumfield')." ff ON ff.fid=f.fid WHERE f.fid='$moveto' AND f.status='1' AND f.type<>'group'");
        if(!$toforum || $_G['fid'] == $toforum['fid']) {
            continue;
        }

        // 关联板块允许发表的特殊主题
        $toforumallowspecial = array(
            1 => $toforum['allowpostspecial'] & 1,
            2 => $toforum['allowpostspecial'] & 2,
            3 => isset($_G['setting']['extcredits'][$_G['setting']['creditstransextra'][2]]) && ($toforum['allowpostspecial'] & 4),
            4 => $toforum['allowpostspecial'] & 8,
            5 => $toforum['allowpostspecial'] & 16,
            127 => $_G['setting']['threadplugins'] ? unserialize($toforum['threadplugin']) : array(),
        );

        // 系统自带的特殊主题
        if($special != 127) {
            $allowmove = $toforum['allowpostspecial'] ? $toforumallowspecial[$thread['special']] : 0;
        } else {
            // 特殊主题插件
            if($toforumallowspecial['127']) {
                $posttable = getposttablebytid($thread['tid']);
                $message = DB::result_first("SELECT message FROM ".DB::table($posttable)." WHERE tid='$thread[tid]' AND first='1'");
                $sppos = strrpos($message, chr(0).chr(0).chr(0));
                $specialextra = substr($message, $sppos + 3);
                $allowmove = in_array($specialextra, $toforumallowspecial[127]);
            } else {
                $allowmove = 0;
            }
        }

        if($allowmove) {
            DB::query("INSERT INTO ".DB::table('forum_thread')." (fid, posttableid, readperm, price, typeid, author, authorid, subject, dateline, lastpost, lastposter, displayorder, digest, special, attachment, moderated, replies, status, isgroup, closed)
    VALUES ('$moveto', '$posttableid', '$readperm', '$price', '$typeid', '$author', '$_G[uid]', '$subject', '$_G[timestamp]', '$_G[timestamp]', '$author', '$displayorder', '$digest', '$special', '$attachment', '$moderated', '1', '$thread[status]', '1', '$tid')");
        }
    }
}

好了, 现在群组和板块关联已经搞定了.

Discuz!X 1.5其实已经自带了群组主题推送到板块这个功能, 只不过需要手动操作, 略显麻烦.

在查看其推送到板块的代码后发现其实就相当于将主题copy一份到forum_thread表, 不过fid变为推送到不板块的fid, tid自增, closed为原主题tid

根据这个思路, 建立一个配置文件, 在群组发表主题时判断该群组是否有关联, 之后copy一条数据就行了.

ok. 到此结束了, 我这正测试, 看看有没有啥不良反映, 大家如果发现了啥问题, 可以联系我.

配置windows下sphinx增量索引手记


在开启searchd守护进程时需要加上参数--pidfile 这样就能生成pid文件到指定的目录中..
之后在合并索引的时候 indexer --merger main delta -c csft_discuz.conf --rotate

完整过程

indexer -c csft_discuz.conf --all
searchd -c csft_discuz.conf --pidfile
之后更新增量索引
indexer delta -c csft_discuz.conf
合并索引
indexer --merger main delta -c csft_discuz.conf --rotate

搞定收工..

PHP ActiveRecord

tablepre = TABLE_PREFIX;
        $this->class = get_class($this);
        $this->table = $this->tablepre . strtolower($this->class);
        $this->data = array();
        $this->connect();
    }

    private function connect() {
        if(!self::$link) {
            self::$link = mysql_connect(DBHOST, DBUSER, DBPASS);
            mysql_select_db(DBNAME);
        }
        return self::$link;
    }

    public function __set($name, $value) {
        $this->data[$name] = $value;
    }

    private function implodefields($cond) {
        $fields = array();
        foreach($cond as $key => $value) {
            $value = mysql_real_escape_string($value);
            $fields[] = "`$key`='$value'";
        }
        return implode(', ', $fields);
    }

    public function add() {
        $fields = $this->implodefields($this->data);
        $sql = "INSERT INTO `{$this->table}` SET $fields";
        $this->query($sql);
    }

    public function findById($id) {
        $sql = "SELECT * FROM `{$this->table}` WHERE `{$this->primaryKey}`='$id' LIMIT 1";
        $data = $this->getOne($sql);
        return $this->makeObjFromArray($data);
    }

    private function makeObjFromArray($data) {
        $obj = new $this->class;
        foreach($data as $key => $value) {
            $obj->$key = $value;
        }
        return $obj;
    }

    private function query($sql) {
        echo $sql . "\n";
        return mysql_query($sql, self::$link);
    }

    private function getOne($sql) {
        $data = $this->query($sql);
        if($data) {
            $item = mysql_fetch_assoc($data);
            return $item;
        }
        return false;
    }
}

class User extends ActiveRecord {
    var $primaryKey = 'id';
}

$user = new User();
$user->name = 'Woody';
$user->email = 'xxx@hotmail.com';
$user->add();

$user = $user->findById(1);
print_r($user);

原文地址:  http://www.phpig.net/showthread.php?tid=310

为什么不用mysql_pconnect[PHP架构师之路]

很好奇为什么PHP没有数据库连接池,就翻了下PHP中mysql模块的源码,发现mysql_pconnect使用zend引擎的函数pemalloc()将数据库连接资源存在了全局内存中。这样看来,mysql_pconnect的效率应该很高,但是为什么很少有人用呢?

1.正常情况下当一个链接断开,你锁的表也会跟着解锁。但是长链接却永远不断开的,所以一个表万一一不小心锁了就一直锁着,除非你等着链接超时或者杀掉进程。同样的锁的问题也会在处理事务的时候发生。

2.正常情况下当一个链接断开,临时表也会被删除。但是由于长链接并不断开,临时表就变得不那么临时了。所以,临时表如果在完成业务后没有被删除,将会继续存在,提供给复用这个长链接的客户端使用。

3.如果PHP和mysql在同一台服务器上或者同一个局域网内,链接的时间是可以忽略不计的,所以用长链接不会得到任何益处。

4.Apache不能很好的处理长链接。当Apache接收到一个新的客户端的请求,它并不会使用一个已经拥有可用长链接的子进程,而是生成一个新的子进程,这个子进程会打开一个新的数据库连接。这就造成空闲的额外进程,资源的浪费,并且,当达到最大连接数时还会导致错误。得不偿失啊~

看看其他人怎么说的?

有一个哥们说:(这哥们是老外,我直接翻译了)

一般情况下,你是不会想用mysql_pconnect的。这个函数是为了连接数据库高开销的场景设计的。典型的Mysql / Apache / PHP 场景中,Apache会创建很多空闲的子进程来等待web请求。这里的每个子进程都会打开并维持一个独立的Mysql链接。所以,如果Mysql Server限制50个链接,而Apache有大于50个的子进程在跑,每个子进程都会建立一个独立的Mysql链接,哪怕他们是空闲的(空闲的httpd子进程不跟其它进程共享Mysql链接)。因此,即使你只有很少几个需要链接数据库的页面跑在一个繁忙的站点上,也会很快用光连接数,而实际上并没有全部被利用上。

所以,用mysql_connect()链接Mysql吧,除非,每次建立连接都要花去大把的时间。

看看官方怎么说的:(地址:http://devzone.zend.com/node/view/id/686#fn1)

The mysql_pconnect() function was designed to provide a mechanism for reducing the cost of establishing and closing connections to the MySQL server. Unfortunately, due to an interaction between the architecture of the Apache server and the architecture of PHP, high traffic on a site that used pconnects could quickly clog up the MySQL server with many unused connections that could prevent many of the active connections from accessing the database.

翻译下:

mysql_pconnect()函数设计的目的是为了提供一种机制来减少与Mysql服务器建立/断开连接的开销。不幸的是,由于Apache服务器架构和PHP架构之间的相互作用,一个使用长链接的高流量的网站会因为很多没有使用的链接迅速堵塞Mysql服务器,并且导致活跃的链接无法访问数据库。

最后,俺们再看下手册上咋写的:

警告:在使用永久连接时还有一些特别的问题需要注意。例如在永久连接中使用数据表锁时,如果脚本不管什么原因无法释放该数据表锁,其随后使用相同连接的脚本将会被永久的阻塞,使得需要重新启动 httpd 服务或者数据库服务。另外,在使用事务处理时,如果脚本在事务阻塞产生前结束,则该阻塞也会影响到使用相同连接的下一个脚本。不管在什么情况下,都可以通过使用 register_shutdown_function() 函数来注册一个简单的清理函数来打开数据表锁,或者回滚事务。或者更好的处理方法,是不在使用数据表锁或者事务处理的脚本中使用永久连接,这可以从根本上解决这个问题(当然还可以在其它地方使用永久连接)。

好了,就写到这了。你还敢用mysql_pconnect()吗??

原文地址: PHP架构师之路

使用PHPDocumentor来生成API文档

使用PHPDocumentor来生成API文档

(一)什么是PHPDocumentor

PHPDocumentor是使用PHP开发的一款API文档生成工具, 相对于有规范注视的程序, 可以使用它快速的生成具有相互参照,索引等功能的API文档, 大概就是这么一个东西, 如果你还不了解. 请继续往下看.

(二)安装PHPDocumentor

安装方式有两种

  1. 手动安装

www.phpdoc.org下载, 然后解压即可

  1. 通过pear自动安装

命令行下输入: pear install PhpDocumentor

(三)使用PHPDocumentor

1.命令行方式

在PHPDocumentor目录下找到phpdoc.bat文件进行编辑. 查找SET phpCli=这句将=号后面的值设置为你的phpcli所在路径, 一般都在你的PHP安装目录下. 比如我的就设置为SET phpCli=C:\wamp\bin\php\php5.3.0\php.exe,保存退出. 在命令行方式下进入PHPDocument目录. 输入phpdoc –h 会出现一大堆参数的介绍, 这里我们只会用到.

-f 要进行分析的文件名称,多个文件用,号分割

-d 要进行分析的目录, 多个目录用,号分割

-t 分析后的文件存放路径

-o 输出文档的格式, 列如: -o HTML:frames:default, 这里你可以打开PHPDocumentor目录下的phpDocumentor/Converters/目录查看. 像上面这个就是对应的phpDocumentor/Converters/HTML/frames/templates/default目录

下面我们写一个文件来测试一下.

Demo.php文件

然后我们打开命令行, 进入PHPDocumentor目录, 输入以下内容

phpdoc -f E:/www/demo.php -t E:/www/demo -o HTML:frames:def

ault

然后打开E:/www/demo/目录打开index.html文件看看. 是不是demo.php文件中的类以及方法全列出来了. 弄到这你已经会使用PHPDocumentor了.  细心的你可能发现demo.php文件中的中文显示出来全乱码了, 是的, 我们还需要做下面的事.

打开 phpDocumentor/Converters/HTML/frames/templates/default/templates/目录中的header.tpl以及top_frame.tpl文件将其中的charset=iso-8859-1改为charset=utf-8,保存文件退出, 重新运行一次phpdoc -f E:/www/demo.php -t E:/www/demo -o HTML:frames:default, 再次浏览生成的文件. 乱码问题就解决了, 你还可以更改模板文件, 把生成的页面弄的更好看..

还有一种方式为web页面生成, 很简单这里不在讲述, 我们主要看看phpdocumentor的注释语法

注释规则:

所有注释已/**开始, 第一行为功能简述,第二行为详细说明,第三行为标记tag.

例如:
/**
     * hello函数
	 *
	 * 传入你的名字, 想你问好.
	 *
	 * @param	string	$name	你的名字
*/
function hello($name)
{
	echo ‘hell’. $name;
}

这样就是一个标准的注释格式了

在上面出现了@param这样的关键字, 这个是用来描述一个参数的. 类似的关键字还有

@access
@author
@copyright
@example
@const
@final
@filesource
@global
@link
@package
@abstruct
@return
@param
@static
@var
@version
@todo

常用的大概就这些, 具体函数请google一下..

PHPDocumentor能够识别的PHP关键字:

Include,include_once
Request,request_once
Define
Function
Global
Class

也就是说你在上面这些列出的关键字前书写规范的注释PHPDocumentor都能进行解析. 都能生成对应的文档, 还等什么自己写个文件试试吧..

GPC的思考:$_REQUEST、$_GET、$_POST、$_COOKIE 的关系和区别

原文地址:http://www.phpig.net/showthread.php?tid=206&pid=548#pid548

GPC的优先级在PHP.INI里可以设置,variables_order = “GPCS”

测试开始。
测试1:

echo '
';//源格式打印
//URL加上m=xxoo来测试$_GET
    print_r($_REQUEST);//首先打印内容
    print_r($_GET);

    $_REQUEST = array();//清空$_REQUEST
    echo $_GET['m'];
echo '

';

测试结果:$_GET['m']的值是xxoo。

测试2:

echo '
';//源格式打印
//URL加上m=xxoo来测试$_GET
    print_r($_REQUEST);
    print_r($_GET);

    $_GET = array();//清空
    print_r($_REQUEST);
echo '

';

测试结果:$_REQUEST['m']的值是xxoo。

结论:对$_REQUEST的操作没有影响到$_GET,反之亦然。$_REQUEST只是包含了一个$_GET的副本(the same as $_POST 哈哈)。

————————————华丽的分隔线 —————————————————
好。继续
测试3:

//POST-GET重名测试
//从其他页面POST传送一个m的值为post,action地址加上m=get
    print_r($_REQUEST);
    print_r($_GET);
    print_r($_POST);

结果:$_REQUEST['m'] == ‘post’。
结论:同时提交GET和POST且同名,$_REQUEST取的是POST的值。(可能是POST优先级更高。)

测试4

//cookie与post\get 重名测试
setcookie('m', 'cookie', time()+3600);//先把COOKIE种上,名字为m,值为cookie。然后刷新。(COOKIE要刷新才生效)

//从其他页面POST传送一个m的值为post,action地址加上m=get
print_r($_REQUEST);

结果:$_REQUEST['m'] == ‘cookie’。结论:COOKIE的优先级最高。

总结:
$_REQUEST、$_GET、$_POST、$_COOKIE 的关系和区别。
1.关系:$_REQUEST包含了$_GET、$_POST、$_COOKIE的所有内容,是它们的集合体。
2.$_GET、$_POST、$_COOKIE在$_REQUEST中都有一个副本。改变$_REQUEST的值不影响$_GET等,反之亦然。
3.GET和POST同名的情况下,$_REQUEST取的是POST的值。COOKIE与GET或POST重名的情况下,$_REQUEST取的是COOKIE的值。COOKIE的优先级最高。

pack/unpack 实现简单的文件数据库

dbhandler) {
            return $this->dbhandler;
        }
        $this->dbhandler = fopen($this->dbfile, "ab+");
        flock($this->dbhandler, LOCK_EX);
        if (!$this->dbhandler) {
            trigger_error("Connot open file {$file}", E_USER_ERROR);
        }
        return $this->dbhandler;
    }

    /**
     * 写入数据
     *
     * @access  public
     * @param   string $str    数据已空格( )分割自断
     * @return  integer
     */
    public function write($str)
    {
        if (!$this->dbhandler) {
            trigger_error('Not open the database', E_USER_ERROR);
        }
        $data  = explode(' ', $str);
        $name  = pack('a8', $data['0']); //a这里用a是为0保证 不管数据多长或多短都格式化为一个长度为8的字符串
        $age   = pack('S', $data['1']); //16位无符合短整数, 总是16位
        $site  = pack('a30', $data['2']); //同理
        return fwrite($this->dbhandler, $name . $age . $site);
    }

    /**
     * 读取数据
     *
     * @access  public
     * @param   integer $row    读取行数 -1为全部读取
     * @return  array
     */
    public function read($row = 0)
    {
        if (!$this->dbhandler) {
            trigger_error('Not open the database', E_USER_ERROR);
        }
        rewind($this->dbhandler);
        if ($row == -1) { /* 读取所有 */
            $res = array();
            fseek($this->dbhandler, 0, SEEK_END);
            $len = ftell($this->dbhandler);
            $len = $len / 40;
            for ($i = 0; $i < $len;$i++) {
                $res[] = $this->read($i);
            }
            return $res;
        } else {
            /* 读取一行 */
            fseek($this->dbhandler, 40 * $row);
            $res = array();
            $res[] = array_shift(unpack('a8', fread($this->dbhandler, 8)));
            $res[] = array_shift(unpack('S', fread($this->dbhandler, 2)));
            $res[] = array_shift(unpack('a30', fread($this->dbhandler, 30)));
            return $res;
        }
    }

    /**
     * 删除记录
     *
     * @access  public
     * @param   integer $row    删除的行数
     * @return  boolean
     */
    public function remove($row)
    {
        $res    = $this->read(-1);
        $status = false;
        if (isset($res[$row])) {
            unset($res[$row]);
            $status = true;
        }
        $len = count($res);
        $this->truncate(); //清空数据库
        for ($i = 0; $i < $len; $i++) {
            isset($res[$i]) && $this->write(join(' ', $res[$i])); //重新写入数据
        }
        return $status;
    }

    /**
     * 清空数据库
     *
     * @access  public
     * @return  void
     */
    public function truncate()
    {
        $this->close();
        unlink($this->dbfile);
        $this->open();
    }

    /**
     * 关闭数据库
     *
     * @access  public
     * @return  boolean
     */
    public function close()
    {
        if (!$this->dbhandler) {
            trigger_error('Not open the database', E_USER_ERROR);
        }
        return fclose($this->dbhandler);
    }
}

$db = new DataBase();
$db->open(); //打开数据库
$db->write('xiaokai 18 www.youyuge.com'); //写入数据
$db->write('likai 18 www.youyuge.com');
$db->read(0); //读取第一条数据
$res = $db->read(-1); //读取所有数据
$db->remove(0); //删除第一条数据
$db->truncate(); //清空数据库
$db->close(); //关闭数据库

为了更了解pack/unpack这两个函数, 仿照别人的例子, 自己添加了些功能…