TOP  >  なーお'nぶろぐ  >  なーお  >  Web  >  [xoops] フォーラムモジュ-ル「xcforum」製作開始

なーお さんの日記

 
2012
2月 2
(木)
10:20
[xoops] フォーラムモジュ-ル「xcforum」製作開始
本文

d3forumの後継フォーラム・掲示板モジュール、「xcforum」を作り始めました。 :-)
自分にはかなーりハードルが高いのですが、 facebookのグループで色々相談しながらできそうなんで、もしかしたらできるかも。

 facebook内の当該スレ

 それに、「factory」という、モジュール自動生成サイトがるのでこれを利用します。

で、今後の参考になるかもしれないので、できるだけメモを残しておきます。

factory サイトで作成。

factoryで作る時

    • テーブル作成時に主キーに割り当てられたフィールドは削除せず、必要なら名称変更して使うこと。
    • status、posttimeは不要でも残しておく? ・・ 要調査

テーブル名の変更

    •  出来たファイル群のsql/mysql.phpを、流用対象モジュールのものを使って書き変える。
       この時、テーブル名に「-」(アンダースコア)や、その他英数字以外のがあると以下の部分でコケる。 テーブル名は英数字に変更。class/Module.class.php の中の
      private function _createAction()
      1
      2
      3
      4
      
      		if(!ctype_alnum($this->mActionName))
      		{
      			return false;
      		}
    • 上で残したフィールドや、category_idなどのフィールドは上書きしないように注意。。

アクセス者が所属するグループRole

Role 配列

1
$this->mRoot->mContext->mUser->_mRoles
1
$this->mRoot->mContext->mUser->isInRole('Module.'.$this->mAsset->mDirname.'.Admin')

protected function _getTitle()

 __EditAction.class.php に、この関数を追記しないと、「title」フィールドが無い場合にNotice。

以後、コメントで続けます。

閲覧(34199)

コメント一覧

投稿ツリー


なーお  投稿日時 2012/2/11 11:09 | 最終変更

DB操作を管理画面に移動

factoryで作ったモジュールの、DB操作を管理画面に移動する。 どうやっていいのか最初は全然わからず、迷子になって右往左往。

いくつか注意するところがあるので、メモ。

ファイル名と配置

admin/actions/
  • TablenameActionnameAction.class.php
    • factoryが作ったActionファイルを、admin内に移動。
    • クラス名の変更。途中に「Admin_」を追記。
       Class Modulename_Admin_TablenameActionname extends hogefuge
       これを忘れると、クラス見つからずリダイレクトされてしまい、どこでリダイレクトされているのか見つけるのに時間をかなり浪費する。
    • protected function _setupActionForm() のオーバーライド(アクションがEdit操作の場合)
       第3引数に「true」が返らないと、adminフォルダ内のformをロードしてくれないので、関数をオーバーライドしておく。以下の例では、予め $this->mod_isadmin にmod_admin判定値を入れておく例。
          protected function _setupActionForm()
          {
              $this->mActionForm =& $this->mAsset->getObject('form', $this->_getConst('DATANAME'), $this->mod_isadmin, $this->_getActionName());
              $this->mActionForm->prepare();
          }
admin/forms/
  • TablenameActionnameForm.class.php
  • TablenameFilterForm.class.php
admin/templates

 任意のテンプレートを使うが、actionスクリプト内の function executeViewInput にある行を以下のように直接指定に書き換え。

$render->setTemplateName('your_template_name.html');

class/handler/

  • Tablename.class.php
     このファイルは移動せず、元の位置に置いておく。
mizuki  投稿日時 2012/2/16 13:39

おお、うれしいですね!

可能であれば、画像やテキストキャプチャーなんかでゲスト投稿できるといいな。

画像なんかも簡単にアップできれば・・なんて!

なーお  投稿日時 2012/2/16 22:41 | 最終変更

mizukiさん、こんにちは。

書き忘れましたが、xcforumはXCL2.2以上限定になります。まあ今後の新規モジュールなので、それで良いかなと。

引用:
可能であれば、画像やテキストキャプチャーなんかでゲスト投稿できるといいな。

画像なんかも簡単にアップできれば・・なんて!

画像やテキストキャプチャーって、意味が良く判らないのですがどういったことですか? 参考までに教えてください。

画像については、leimgモジュールなどを使えるようにするつもりです。 また、nao-ponさんがxelfinderモジュールも開発中ですので、そちらとの連携も考えています。 どうぞ楽しみに。

それから、pack2012では将来的に、TokyoPenのように、モジュールのアップデータも実装するべく、開発中です。 :-)

mizuki  投稿日時 2012/2/17 0:07
引用:

書き忘れましたが、xcforumはXCL2.2以上限定になります

ベータ版人柱OKです!

引用:

画像やテキストキャプチャーって、意味が良く判らないのですがどういったことですか? 参考までに教えてください。

画像認証 captcha
http://linux.ohwada.jp/modules/wfdownloads/singlefile.php?cid=1&lid=73

テキストキャプチャーっていうのが、ELGGっていうCMS系のプラグインで見たんですが、

3×2

って表示されたら、6という感じで、答えを入力するとか、

日本一高い山は?

って表示されたら、富士山という感じで、答えを入力し、投稿ボタンをクリックする・・とかです。

ちょっと紛らわしかったですね。

なーお  投稿日時 2012/2/18 0:41 | 最終変更

factoryで生成されたソースコードの実行順

理解できるまで時間がかかったが、要するに「class/Module.class.php」の public function execute(/*** XCube_Controller ***/ &$controller) に書かれた順番で実行される。

  1. _createAction()
  2. prepare()
    • _setupObject()
  3. hasPermission()
  4. execute()
  5. getDefaultView()
  6. 'executeView' . ucfirst($viewStatus)

この順番を変えるにはこのファイル内の記述を変更すれば可能だが、まずそれはしないので、この順番を頭に入れて読むと実行の流れがわかってソースコードを読みやすい! :-)

なーお  投稿日時 2012/2/28 21:17 | 最終変更

criteriaで JOINを実現するための、現実的で応用の効く一つの解。(by suinさん)

1
‎$query = "SELECT * FROM $tbl1 JOIN $tbl2" . $criteria->render();
なーお  投稿日時 2012/3/7 12:04 | 最終変更

[xcforum開発] 徐々に進んでます。 :-)

壁にぶつかっていたのは、TopicsList表示で 複数テーブルをJOINして複数の検索条件を付加する部分。 JOINせずにロジックを回していたんだけど、長くなるし見通しも悪いコードになりそうな悪寒。。

複数テーブルをJOINすること自体、XoopsObject・criteriaではできない。 そこでfacebook上でヒントをいただき、d3forumの元のSQLで抽出し、 表示だけをfactoryが生成するobjetに放り込む方法を取ることで、解決。 ユーザーがチェック済みの投稿のアイコンを変える部分なども問題無くクリアできた。 ;-)

TopicsList.jpg

ところで、d3forumはフォーラム内に独自の検索があって、このクエリ文字をfactoryで作られたPageNaviに反映する方法が今一つ判らない。今のままだと、実際のクエリ結果とPageNaviの表示がバラバラ。 テンプレート内にはsmartyプラグインが書かれていて、プラグインからpageNaviのオブジェクト内メソッドをあれこれ呼んで作っているので、まあその辺を解析して割り込めれば何とかなると思うんだけど・・

最終的には見た目もBoxNavi的にしたいし、これはd3forumで組み込んだクラスをやっぱり使うのがいいかな。 :roll:
他方のソート機能はテンプレートにクエリ文字分を付加するだけでOKだったから、そちらは有り難く使わせていただきます!

以下、今回の実装に関連するソースコード部分。

ハンドラクラス

Legacy_AbstractClientObjectHandler を継承したクラス内に、最低限、以下の2つのメソッドを実装。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
    public function &getObjects($criteria = null, $limit = null, $start = null, $id_as_key = false, $custom_sql = null, $add_init = 0 )
    {
        $ret = array();
 
 
        if ( !$custom_sql ){
            return parent::getObjects($criteria, $limit, $start, $id_as_key);
        } else {
            $sql = $custom_sql;
            if ($limit === null) {
                $limit = $criteria->getLimit();
            }
 
            if ($start === null) {
                $start = $criteria->getStart();
            }
 
            $sorts = array();
            foreach ($criteria->getSorts() as $sort) {
                $sorts[] = '`' . $sort['sort'] . '` ' . $sort['order'];
            }
 
            if ($criteria->getSort() != '') {
                $sql .= " ORDER BY " . implode(',', $sorts);
            }
 
            $result = $this->db->query($sql, $limit, $start);
 
            if (!$result) {
                return $ret;
            }
 
            while($row = $this->db->fetchArray($result)) {
                $obj =new $this->mClass();
                if ($add_init>=1){   // added
                    $obj->initAdditionalFields( $add_init );
                }
                $obj->mDirname = $this->getDirname();
                $obj->assignVars($row);
                $obj->unsetNew();
 
                if ($id_as_key)    {
                    $ret[$obj->get($this->mPrimary)] =& $obj;
                }
                else {
                    $ret[]=&$obj;
                }
 
                unset($obj);
            }
 
            return $ret;
        }
 
    }
 
    function getCount($criteria = null, $custom_sql = null )
    {
        if ( !$custom_sql ){
            return parent::getCount($criteria);
        } else {
            $sql = "SELECT COUNT(*) c ".$custom_sql;
            return $this->_getCount($sql);        }
    }

オブジェクトクラス

Xcforum_TopicsObject 内に、オブジェクト内メンバ変数を後処理で初期化するメソッドを追加して、topicsテーブル内にJOINしたテーブルデータや他の諸々のデータも、Object内に格納できるようにする。 呼び元は上記getObject()中にあり。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    public function initAdditionalFields( /*** int ***/ $init_mode = 0 )
    {
            // additional items
            $this->initVar('lp_post_text', XOBJ_DTYPE_TEXT, '', false);
            $this->initVar('lp_subject', XOBJ_DTYPE_STRING, '', false, 255);
            $this->initVar('lp_icon', XOBJ_DTYPE_INT, '', false);
            $this->initVar('lp_number_entity', XOBJ_DTYPE_INT, '', false);
            $this->initVar('lp_special_entity', XOBJ_DTYPE_INT, '', false);
            $this->initVar('lp_guest_name', XOBJ_DTYPE_STRING, '', false, 25);
 
            $this->initVar('fp_post_text', XOBJ_DTYPE_TEXT, '', false);
            $this->initVar('fp_subject', XOBJ_DTYPE_STRING, '', false, 255);
            $this->initVar('fp_icon', XOBJ_DTYPE_INT, '', false);
            $this->initVar('fp_number_entity', XOBJ_DTYPE_INT, '', false);
            $this->initVar('fp_special_entity', XOBJ_DTYPE_INT, '', false);
            $this->initVar('fp_guest_name', XOBJ_DTYPE_STRING, '', false, 25);
 
            $this->initVar('u2t_time', XOBJ_DTYPE_INT, '', false);
            $this->initVar('u2t_marked', XOBJ_DTYPE_INT, '', false);
            $this->initVar('u2t_rsv', XOBJ_DTYPE_INT, '', false);
 
        if ($init_mode >= 2){
            $this->initVar('forum_title', XOBJ_DTYPE_STRING, '', 255);
        }
 
    }

SQLとgetObjects

そして、肝心のObject取得部分は、以下の感じ。 現時点ではコントローラに書いちゃっているけど、もう少し粒度を細かくして、共通クラスに放り込んでブロックからも使えるようにできるといいかなあ。。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
    public function getDefaultView()
    {
        $handler = Legacy_Utils::getModuleHandler('forums', $this->mAsset->mDirname);
        if ( isset($this->forum_id) && $this->forum_id > 0 ){
            $this->mForumObj = $handler->get($this->forum_id);
        } else {
            $this->mForumObj = $handler->create();
        }
 
        // set moderators
        $handler->setModerateGroups( $this->mForumObj, $this->forum_id );
        $handler->setModerateUsers( $this->mForumObj, $this->forum_id );
 
        $this->mFilter =& $this->_getFilterForm();
        $this->mFilter->fetch();
 
        $handler =& $this->_getHandler();
        $mCriteria = $this->mFilter->getCriteria();
        $mCriteria->add(new criteria('forum_id', $this->mForumAcc['can_read'], 'IN'));
 
        $db = $this->mRoot->mController->mDB;
        $dirname = $this->mAsset->mDirname;
        $uid = $this->mRoot->mContext->mUser->isInRole('Site.RegisteredUser') ? $this->mRoot->mContext->mXoopsUser->get('uid') : 0 ;
 
        // forum_id
        if ( $this->forum_id > 0 ){
            $whr_forum = 't.forum_id=' . $this->forum_id;
            $isadminormod = $this->mod_isadmin ? true : in_array( $this->forum_id, $this->mForumAcc['moderate']);
            $fields_forum = "";
            $join_forum = "";
            $initial_mode = 1;
        } else {
            $whr_forum = 't.forum_id IN (' . implode(',', $this->mForumAcc['can_read']) . ') ';
            $isadminormod = $this->mod_isadmin ? true : false ;
            $fields_forum = ", f.forum_id, f.forum_title, f.forum_external_link_format ";
            $join_forum = " LEFT JOIN ".$db->prefix($dirname."_forums")." f ON f.forum_id=t.forum_id ";
            $initial_mode = 2;
        }
 
 
        // INVISIBLE
        $whr_invisible = $isadminormod ? '1' : '! t.topic_invisible' ;
 
        // SOLVED
        $this->solved_options = array(
            0 => '----' ,
            1 => _MD_XCFORUM_OPT_SOLVEDYES ,
            2 => _MD_XCFORUM_OPT_SOLVEDNO ,
        ) ;
 
        $solved_sqls = array(
            0 => '1' ,
            1 => 't.topic_solved=1' ,
            2 => 't.topic_solved=0' ,
        ) ;
        if( empty( $this->mod_config['use_solved' ] ) ) {
            // disable "solved function"
            $query4assign['solved'] = 0 ;
            $whr_solved = $solved_sqls[0] ;
        } else {
            $q_solved = (int)$this->mRoot->mContext->mRequest->getRequest('solved');
            if( ! empty( $this->solved_options[$q_solved] ) ) {
                $query4assign['solved'] = $q_solved ;
                $whr_solved = $solved_sqls[ $query4assign['solved'] ] ;
            } else {
                $query4assign['solved'] = 0 ;
                $whr_solved = $solved_sqls[0] ;
            }
        }
 
        // TXT
        $myts =& Xcforum_Utils::getMytextSanitizer();
        $q_txt = $this->mRoot->mContext->mRequest->getRequest('txt');
        //adump($this->mRoot->mContext->mRequest->getRequest('txt'));
        //$q_txt = $_GET['txt'];
        if( ! empty( $q_txt ) ) {
            $txt = $myts->stripSlashesGPC( $q_txt ) ;
            $query4assign['txt'] = htmlspecialchars( $txt , ENT_QUOTES , _CHARSET ) ;
            $txt4sql = addslashes( $txt ) ;
            $whr_txt = "fp.subject LIKE '%$txt4sql%' OR fp.post_text LIKE '%$txt4sql%'" ;
        } else {
            $query4assign['txt'] = '' ;
            $whr_txt = '1' ;
        }
        $this->query4assign = $query4assign;
        $limit = $this->mod_config['topics_per_page'];
 
        // get counts of total topics
        $sql_base = "FROM "
            .$db->prefix($dirname."_topics")." t LEFT JOIN "
            .$db->prefix($dirname."_users2topics")." u2t ON t.topic_id=u2t.topic_id AND u2t.uid=$uid LEFT JOIN "
            .$db->prefix($dirname."_posts")." lp ON lp.post_id=t.topic_last_post_id LEFT JOIN "
            .$db->prefix($dirname."_posts")." fp ON fp.post_id=t.topic_first_post_id
            ".$join_forum."
          WHERE ($whr_forum) AND ($whr_invisible) AND ($whr_solved) AND ($whr_txt)" ;
 
        $sql = $sql_base;
 
        $this->topic_hits = $handler->getCount( $mCriteria, $sql );
 
        // get topic objects
        $sql = "SELECT t.*, lp.post_text AS lp_post_text, lp.subject AS lp_subject, lp.icon AS lp_icon,
          lp.number_entity AS lp_number_entity, lp.special_entity AS lp_special_entity,
          lp.guest_name AS lp_guest_name, fp.subject AS fp_subject, fp.icon AS fp_icon,
          fp.number_entity AS fp_number_entity, fp.special_entity AS fp_special_entity,
          fp.guest_name AS fp_guest_name, u2t.u2t_time, u2t.u2t_marked, u2t.u2t_rsv
          ".$fields_forum.$sql_base;
 
        // No6 argument is for additional initializztion : 1::forum_id>0, 2::forum_id==0
        $this->mObjects =& $handler->getObjects( $mCriteria, NULL, NULL, false, $sql, $initial_mode );
 
        return XCFORUM_FRAME_VIEW_INDEX;
    }
なーお  投稿日時 2012/3/11 13:19 | 最終変更

factoryが生成するモジュールinstallコードで、notificationがインストールされない問題が発覚したので、 その解決までをメモっておきます。

html/modules/legacy/admin/class/ModuleInstallInformation.class.php の
function _loadNotificationPreferenceInfomations(&$modversion, &$collection)
内の頭に atrace();をしてみたところ、

従来モジュール (GNAViの例)

Everything is expanded.Everything is shortened.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 
 
 
 
 
 
 
 
 
 
 
 
#0 /html**/modules/legacy/admin/class/ModuleInstallInformation.class.php(658): atrace()
#1 /html**/modules/legacy/admin/class/ModuleInstallInformation.class.php(784): Legacy_ModinfoX2FileReader->_loadNotificationPreferenceInfomations(Array, Object(Legacy_PreferenceInfoCollection))
#2 /html**/modules/legacy/admin/class/ModuleInstallUtils.class.php(722): Legacy_ModinfoX2FileReader->loadPreferenceInformations()
#3 /html**/modules/legacy/admin/class/ModuleInstaller.class.php(188): Legacy_ModuleInstallUtils->installAllOfConfigs(Object(XoopsModule), Object(Legacy_ModuleInstallLog))
#4 /html**/modules/legacy/admin/class/ModuleInstaller.class.php(254): Legacy_ModuleInstaller->_installPreferences()
#5 /html**/modules/legacy/admin/actions/ModuleInstallAction.class.php(170): Legacy_ModuleInstaller->executeInstall()
#6 /html**/modules/legacy/class/ActionFrame.class.php(153): Legacy_ModuleInstallAction->execute(Object(Legacy_Controller), Object(XoopsUser))
#7 [internal function]: Legacy_ActionFrame->execute(Object(Legacy_Controller))
#8 /html**/core/XCube_Delegate.class.php(356): call_user_func_array(Array, Array)
#9 /html**/core/XCube_Controller.class.php(216): XCube_Delegate->call(Object(XCube_Ref))
#10 /html**/modules/legacy/admin/index.php(24): XCube_Controller->execute()
#11 {main}

factory生成モジュール(xcforumの例)

Everything is expanded.Everything is shortened.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 
 
 
 
 
 
 
 
 
 
 
 
#0 /html**/modules/legacy/admin/class/ModuleInstallInformation.class.php(658): atrace()
#1 /html**/modules/legacy/admin/class/ModuleInstallInformation.class.php(784): Legacy_ModinfoX2FileReader->_loadNotificationPreferenceInfomations(Array, Object(Legacy_PreferenceInfoCollection))
#2 /html**/modules/xcforum/admin/class/installer/XcforumInstallUtils.class.php(872): Legacy_ModinfoX2FileReader->loadPreferenceInformations()
#3 /trust**/modules/xcforum/admin/class/installer/XcforumInstaller.class.php(198): Xcforum_InstallUtils::installAllOfConfigs(Object(XoopsModule), Object(Legacy_ModuleInstallLog))
#4 /trust**/modules/xcforum/admin/class/installer/XcforumInstaller.class.php(276): Xcforum_Installer->_installPreferences()
#5 /html**/modules/legacy/admin/actions/ModuleInstallAction.class.php(170): Xcforum_Installer->executeInstall()
#6 /html**/modules/legacy/class/ActionFrame.class.php(153): Legacy_ModuleInstallAction->execute(Object(Legacy_Controller), Object(XoopsUser))
#7 [internal function]: Legacy_ActionFrame->execute(Object(Legacy_Controller))
#8 /html**/core/XCube_Delegate.class.php(356): call_user_func_array(Array, Array)
#9 /html**/core/XCube_Controller.class.php(216): XCube_Delegate->call(Object(XCube_Ref))
#10 /html**/modules/legacy/admin/index.php(24): XCube_Controller->execute()
#11 {main}

ということで、どうやらXcforumInstaller.class.phpが怪しい。

Xcforum_InstallUtils.class.php の 「public static function installAllOfConfigs」の内容を そっくりそのまま、Legacy_ModuleInstallUtils 内の同メソッドを上書きしてみたら、無事にnotificationもインストールできた。 :-)

867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
    public static function installAllOfConfigs(/*** XoopsModule ***/ &$module,/*** Legacy_ModuleInstallLog ***/ &$log)
    {
        $dirname = $module->get('dirname');
 
        $fileReader =new Legacy_ModinfoX2FileReader($dirname);
        $preferences =& $fileReader->loadPreferenceInformations();
 
        //
        // Preferences
        //
        foreach (array_keys($preferences->mPreferences) as $idx) {
            Legacy_ModuleInstallUtils::installPreferenceByInfo($preferences->mPreferences[$idx], $module, $log);
        }
 
        //
        // Comments
        //
        foreach (array_keys($preferences->mComments) as $idx) {
            Legacy_ModuleInstallUtils::installPreferenceByInfo($preferences->mComments[$idx], $module, $log);
        }
 
        //
        // Notifications
        //
        foreach (array_keys($preferences->mNotifications) as $idx) {
            Legacy_ModuleInstallUtils::installPreferenceByInfo($preferences->mNotifications[$idx], $module, $log);
        }
    }

factoryに書かれたコードが、XCL2.2のLegacyモジュールの変更に追い付いていなかったのかな? :roll:



新しくコメントをつける

題名
ゲスト名
投稿本文
より詳細なコメント入力フォームへ