concrete5管理画面ページ検索機能のコントローラー部を読む
Posted by admin at 13:08 日時 2013/01/25
concrete5には管理画面にページ検索機能があり、非常に優秀です。様々な条件で複合的に検索が可能です。なので、検索機能を開発する場合は非常に参考になるのですが、コメントが全然書かれていないので、さらっと自分なりにコメントを足す目的で、ブログに残しておきます。
管理画面の各ページのコントローラー部は concrete/controllers/dashboard 以下に格納されています。該当のファイルはこちら。
concrete/controllers/dashboard/sitemap/search.php
class DashboardSitemapSearchController extends Concrete5_Controller_Dashboard_Sitemap_Search {}
ところが、中身は別のクラスを継承しているだけの空のクラスです。これは、メソッド単位でのカスタマイズを可能にするための配慮です。もし管理画面のページ検索の挙動をカスタマイズしたい場合は、DashboardSitemapSearchController クラスをオーバーライドすればメソッド単位でのカスタマイズが可能です。
継承元のクラスはこちら
concrete/core/controllers/dashboard/sitemap/search.php
class Concrete5_Controller_Dashboard_Sitemap_Search extends Controller { public $helpers = array('form');
最初の $helpers というインスタンスは、必要なヘルパーが自動的に読み込まれるようにするためのものです。view.php で $form という変数からformヘルパーにアクセスできるようになります。いまいちピンとこないので個人的には使ってませんが。。
参考:How-To: Build a Single-Page Powered Editing Interface for concrete5 Pages
次にview.phpに値を渡す view メソッドの中身です。
public function view() { $html = Loader::helper('html'); $pageList = $this->getRequestedSearchResults(); if (is_object($pageList)) { $searchInstance = 'page' . time(); $this->addHeaderItem('<script type="text/javascript">$(function() { ccm_sitemapSetupSearch(\'' . $searchInstance . '\'); });</script>'); $pages = $pageList->getPage(); $this->set('pageList', $pageList); $this->set('pages', $pages); $this->set('searchInstance', $searchInstance); $this->set('pagination', $pageList->getPagination()); } }
基本的には getRequestedSearchResults メソッドの結果を view に渡しています。が、そこから getPage メソッドの結果や getPagination メソッドの結果も渡しています。この内容は getRequestedSearchResults メソッドの中身を見ないと分からないですね。
$this->addHeaderItem() は header 要素の中に出力を追加できます。
$this->set() で view に値を渡すことができます。$this->set(‘pages’, $pages); の場合であれば view.php で $pages として呼び出せます。
次に、getRequestedSearchResults メソッドの中身です。
public function getRequestedSearchResults() { $dh = Loader::helper('concrete/dashboard/sitemap'); if (!$dh->canRead()) { return false; } $pageList = new PageList(); $pageList->ignoreAliases(); $pageList->enableStickySearchRequest(); if ($_REQUEST['submit_search']) { $pageList->resetSearchRequest(); } $req = $pageList->getSearchRequest(); $pageList->displayUnapprovedPages(); $pageList->sortBy('cDateModified', 'desc'); $columns = PageSearchColumnSet::getCurrent(); $this->set('columns', $columns); $cvName = htmlentities($req['cvName'], ENT_QUOTES, APP_CHARSET); if ($cvName != '') { $pageList->filterByName($cvName); } if ($req['numResults'] && Loader::helper('validation/numbers')->integer($req['numResults'])) { $pageList->setItemsPerPage($req['numResults']); } if ($req['ctID']) { $pageList->filterByCollectionTypeID($req['ctID']); } if (is_array($req['selectedSearchField'])) { foreach($req['selectedSearchField'] as $i => $item) { // due to the way the form is setup, index will always be one more than the arrays if ($item != '') { switch($item) { case 'keywords': $keywords = htmlentities($req['keywords'], ENT_QUOTES, APP_CHARSET); $pageList->filterByKeywords($keywords); break; case 'num_children': $symbol = '='; if ($req['cChildrenSelect'] == 'gt') { $symbol = '>'; } else if ($req['cChildrenSelect'] == 'lt') { $symbol = '<'; } $pageList->filterByNumberOfChildren($req['cChildren'], $symbol); break; case 'owner': $ui = UserInfo::getByUserName($req['owner']); if (is_object($ui)) { $pageList->filterByUserID($ui->getUserID()); } else { $pageList->filterByUserID(-1); } break; case 'theme': $pageList->filter('ptID', $_REQUEST['ptID']); break; case 'parent': if (isset($req['_cParentAll'])) { $req['cParentAll'] = $req['_cParentAll']; } if ($req['cParentIDSearchField'] > 0) { if ($req['cParentAll'] == 1) { $pc = Page::getByID($req['cParentIDSearchField']); $cPath = $pc->getCollectionPath(); $pageList->filterByPath($cPath); } else { $pageList->filterByParentID($req['cParentIDSearchField']); } } break; case 'version_status': if (isset($req['_cvIsApproved'])) { $req['cvIsApproved'] = $req['_cvIsApproved']; } $pageList->filterByIsApproved($req['cvIsApproved']); break; case "date_public": $dateFrom = $req['date_public_from']; $dateTo = $req['date_public_to']; if ($dateFrom != '') { $dateFrom = date('Y-m-d', strtotime($dateFrom)); $pageList->filterByPublicDate($dateFrom, '>='); $dateFrom .= ' 00:00:00'; } if ($dateTo != '') { $dateTo = date('Y-m-d', strtotime($dateTo)); $dateTo .= ' 23:59:59'; $pageList->filterByPublicDate($dateTo, '<='); } break; case "last_modified": $dateFrom = $req['last_modified_from']; $dateTo = $req['last_modified_to']; if ($dateFrom != '') { $dateFrom = date('Y-m-d', strtotime($dateFrom)); $pageList->filterByDateLastModified($dateFrom, '>='); $dateFrom .= ' 00:00:00'; } if ($dateTo != '') { $dateTo = date('Y-m-d', strtotime($dateTo)); $dateTo .= ' 23:59:59'; $pageList->filterByDateLastModified($dateTo, '<='); } break; case "date_added": $dateFrom = $req['date_added_from']; $dateTo = $req['date_added_to']; if ($dateFrom != '') { $dateFrom = date('Y-m-d', strtotime($dateFrom)); $pageList->filterByDateAdded($dateFrom, '>='); $dateFrom .= ' 00:00:00'; } if ($dateTo != '') { $dateTo = date('Y-m-d', strtotime($dateTo)); $dateTo .= ' 23:59:59'; $pageList->filterByDateAdded($dateTo, '<='); } break; default: Loader::model('attribute/categories/collection'); $akID = $item; $fak = CollectionAttributeKey::get($akID); if (!is_object($fak) || (!($fak instanceof CollectionAttributeKey))) { break; } $type = $fak->getAttributeType(); $cnt = $type->getController(); $cnt->setRequestArray($req); $cnt->setAttributeKey($fak); $cnt->searchForm($pageList); break; } } } } $this->set('searchRequest', $req); return $pageList; }
長いのでポイントだけ見てみます。
$dh = Loader::helper('concrete/dashboard/sitemap'); if (!$dh->canRead()) { return false; }
これはサイトマップへのアクセス権限があるかどうかですね。
$pageList = new PageList(); $pageList->ignoreAliases(); $pageList->enableStickySearchRequest();
PageListクラスのインスタンスを呼び出しています。PageListクラスはこれだけのクラスを継承しています。親クラスになるほど抽象度が上がっている…はず
PageList < Concrete5_Model_PageList < DatabaseItemList < Concrete5_Library_DatabaseItemList < ItemList < Concrete5_Library_ItemList
ignoreAliases メソッドはエイリアスを無視します。enableStickySearchRequest メソッドは、ページを離れて戻ってきても、セッション中であれば検索結果を保持します。
if ($_REQUEST['submit_search']) { $pageList->resetSearchRequest(); }
入力があった際は検索結果をリセットしています。enableStickySearchRequest してるからですね。
このあたりの検索対象に限らず検索処理に共通であるメソッドは ItemList に書かれていて、公式ドキュメントの System > Search/Sort/Pagination ページで紹介されています。自前で開発した機能でも、検索処理はこのライブラリーを継承したほうが良いとされています。
$req = $pageList->getSearchRequest(); $pageList->displayUnapprovedPages(); $pageList->sortBy('cDateModified', 'desc');
getSearchRequest メソッドで検索リクエストを取得しています。
displayUnapprovedPages メソッドで未承認のページも含めています。これは管理画面の機能ならでは。
sortBy メソッドで検索結果のソートを行なっています。ここでは変更した日時で。
$cvName = htmlentities($req['cvName'], ENT_QUOTES, APP_CHARSET); if ($cvName != '') { $pageList->filterByName($cvName); }
filterByName メソッドでページ名でフィルターしています。
if ($req['numResults'] && Loader::helper('validation/numbers')->integer($req['numResults'])) { $pageList->setItemsPerPage($req['numResults']); }
1ページの表示件数を指定しています。整数のバリデーションにはバリデーション・ヘルパーを使っています。
if (is_array($req['selectedSearchField'])) { foreach($req['selectedSearchField'] as $i => $item) { if ($item != '') { switch($item) {
ここからさらに色んな条件で複合検索をしています。
$keywords = htmlentities($req['keywords'], ENT_QUOTES, APP_CHARSET); $pageList->filterByKeywords($keywords);
キーワード検索
$symbol = '='; if ($req['cChildrenSelect'] == 'gt') { $symbol = '>'; } else if ($req['cChildrenSelect'] == 'lt') { $symbol = '<'; } $pageList->filterByNumberOfChildren($req['cChildren'], $symbol);
子ページ数で絞り込み
$ui = UserInfo::getByUserName($req['owner']); if (is_object($ui)) { $pageList->filterByUserID($ui->getUserID()); } else { $pageList->filterByUserID(-1); }
ページのオーナーで絞り込み
$pageList->filter('ptID', $_REQUEST['ptID']);
ページのテーマで絞り込み
if (isset($req['_cParentAll'])) { $req['cParentAll'] = $req['_cParentAll']; } if ($req['cParentIDSearchField'] > 0) { if ($req['cParentAll'] == 1) { $pc = Page::getByID($req['cParentIDSearchField']); $cPath = $pc->getCollectionPath(); $pageList->filterByPath($cPath); } else { $pageList->filterByParentID($req['cParentIDSearchField']); } }
親ページから子ページを検索。filterByPath だと孫ページ以下も検索。
if (isset($req['_cvIsApproved'])) { $req['cvIsApproved'] = $req['_cvIsApproved']; } $pageList->filterByIsApproved($req['cvIsApproved']);
承認されているかどうか
$dateFrom = $req['date_public_from']; $dateTo = $req['date_public_to']; if ($dateFrom != '') { $dateFrom = date('Y-m-d', strtotime($dateFrom)); $pageList->filterByPublicDate($dateFrom, '>='); $dateFrom .= ' 00:00:00'; } if ($dateTo != '') { $dateTo = date('Y-m-d', strtotime($dateTo)); $dateTo .= ' 23:59:59'; $pageList->filterByPublicDate($dateTo, '<='); }
ページの公開日時で絞り込み。FromとToで絞り込みしています。同様に最終編集日時、追加日時でも処理が書かれています。
ページのフィルター系は他にも色んなメソッドがあり便利です。公式ドキュメントの Pages > Listing & Searching で紹介されています。
Loader::model('attribute/categories/collection'); $akID = $item; $fak = CollectionAttributeKey::get($akID); if (!is_object($fak) || (!($fak instanceof CollectionAttributeKey))) { break; } $type = $fak->getAttributeType(); $cnt = $type->getController(); $cnt->setRequestArray($req); $cnt->setAttributeKey($fak); $cnt->searchForm($pageList);
検索系の処理では一番ややこしいのが属性の処理。属性は非常に汎用的にできていますが、その分オブジェクト間のつながりがいまいち分かりにくい。でもまあ、基本コピペで動く。
$fakって何の略だろう。FileAttributeKeyの処理からコピペして忘れたのかな?