* @link http://www.netcommons.org NetCommons Project * @license http://www.netcommons.org/license.txt NetCommons License */ App::uses('PagesAppModel', 'Pages.Model'); App::uses('Current', 'NetCommons.Utility'); App::uses('Space', 'Rooms.Model'); /** * Page Model * * @author Kohei Teraguchi * @package NetCommons\Pages\Model */ class Page extends PagesAppModel { /** * パブリックスペースのページID * * @var string */ const PUBLIC_ROOT_PAGE_ID = '1'; /** * プライベートスペースのページID * * @var string */ const PRIVATE_ROOT_PAGE_ID = '2'; /** * グループスペースのページID * * @var string */ const ROOM_ROOT_PAGE_ID = '3'; /** * ホームのソートキー * * @var string */ const HOME_SORT_KEY = '~00000001-00000001'; /** * TreeParser * __constructでセットする * * @var array */ public static $treeParser; /** * Default behaviors * * @var array */ public $actsAs = array( 'Pages.PagesTree', 'Pages.SavePage', 'Pages.GetPage', 'Pages.PageAssociations', 'ThemeSettings.Theme', ); /** * Validation rules * * @var array */ public $validate = array(); //The Associations below have been created with all possible keys, those that are not needed can be removed /** * belongsTo associations * * @var array */ public $belongsTo = array( 'Room' => array( 'className' => 'Rooms.Room', 'foreignKey' => 'room_id', 'conditions' => '', 'fields' => '', 'order' => '' ), 'ParentPage' => array( 'className' => 'Pages.Page', 'foreignKey' => 'parent_id', 'conditions' => '', 'fields' => '', 'order' => '' ) ); /** * hasMany associations * * @var array */ public $hasMany = array( 'ChildPage' => array( 'className' => 'Pages.Page', 'foreignKey' => 'parent_id', 'dependent' => false, 'conditions' => '', 'fields' => '', 'order' => '', 'limit' => '', 'offset' => '', 'exclusive' => '', 'finderQuery' => '', 'counterQuery' => '' ), 'PageContainer' => array( 'className' => 'Pages.PageContainer', 'foreignKey' => 'page_id', 'dependent' => false, 'conditions' => '', 'fields' => '', 'order' => '', 'limit' => '', 'offset' => '', 'exclusive' => '', 'finderQuery' => '', 'counterQuery' => '' ), ); /** * Spaceモデルをバインドしているかどうか * * @var array */ protected $_isSpaceBind = false; /** * Constructor. Binds the model's database table to the object. * * @param bool|int|string|array $id Set this ID for this model on startup, * can also be an array of options, see above. * @param string $table Name of database table to use. * @param string $ds DataSource connection name. * @see Model::__construct() * @SuppressWarnings(PHPMD.BooleanArgumentFlag) */ public function __construct($id = false, $table = null, $ds = null) { parent::__construct($id, $table, $ds); self::$treeParser = chr(9); } /** * Called before each find operation. Return false if you want to halt the find * call, otherwise return the (modified) query data. * * @param array $query Data used to execute this query, i.e. conditions, order, etc. * @return mixed true if the operation should continue, false if it should abort; or, modified * $query to continue with new $query * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#beforefind */ public function beforeFind($query) { if (Hash::get($query, 'recursive') > -1 && isset($this->belongsTo['Room'])) { $this->bindModel(array( 'belongsTo' => array( 'Space' => array( 'className' => 'Rooms.Space', 'foreignKey' => false, 'conditions' => array( 'Room.space_id = Space.id', ), 'fields' => '', 'order' => '' ), ) ), true); $this->Room->useDbConfig = $this->useDbConfig; $this->Space->useDbConfig = $this->useDbConfig; } return true; } /** * Called after each find operation. Can be used to modify any results returned by find(). * Return value should be the (modified) results. * * @param mixed $results The results of the find operation * @param bool $primary Whether this model is being queried directly (vs. being queried as an association) * @return mixed Result of the find operation * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#afterfind * @SuppressWarnings(PHPMD.BooleanArgumentFlag) */ public function afterFind($results, $primary = false) { foreach ($results as $i => $result) { if (!isset($result['Space'])) { break; } if ($result['Space']['permalink']) { $results[$i]['Page']['full_permalink'] = $result['Space']['permalink'] . '/'; } else { $results[$i]['Page']['full_permalink'] = ''; } $results[$i]['Page']['full_permalink'] .= $result['Page']['permalink']; } return $results; } /** * Called during validation operations, before validation. Please note that custom * validation rules can be defined in $validate. * * @param array $options Options passed from Model::save(). * @return bool True if validate operation should continue, false to abort * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#beforevalidate * @see Model::save() */ public function beforeValidate($options = array()) { $themes = $this->getThemes(); $themeNames = []; foreach ($themes as $theme) { $themeNames[] = $theme['name']; } $this->validate = ValidateMerge::merge($this->validate, array( 'slug' => array( 'notBlank' => array( 'rule' => array('notBlank'), 'message' => sprintf(__d('net_commons', 'Please input %s.'), __d('pages', 'Slug')), 'required' => true ), 'validPermalink' => array( 'rule' => array('validPermalink'), 'message' => sprintf( __d('pages', 'Use of %s is prohibited. Please enter a different entry.'), __d('pages', 'Slug') ), 'allowEmpty' => false, 'required' => true, ), ), 'permalink' => array( 'notBlank' => array( 'rule' => array('notBlank'), 'message' => sprintf(__d('net_commons', 'Please input %s.'), __d('pages', 'Slug')), 'required' => true ), 'isUniquePermalink' => array( 'rule' => array('isUniquePermalink'), 'message' => sprintf(__d('net_commons', '%s is already in use.'), __d('pages', 'Slug')), ), 'validPermalink' => array( 'rule' => array('validPermalink'), 'message' => sprintf( __d('pages', 'Use of %s is prohibited. Please enter a different entry.'), __d('pages', 'Slug') ), 'allowEmpty' => false, 'required' => true, ), ), 'root_id' => array( 'numeric' => array( 'rule' => array('numeric'), 'message' => __d('net_commons', 'Invalid request.'), ), ), 'is_container_fluid' => array( 'boolean' => array( 'rule' => array('boolean'), 'message' => __d('net_commons', 'Invalid request.'), ), ), 'theme' => array( 'inList' => array( 'rule' => array('inList', $themeNames), 'message' => __d('net_commons', 'Invalid request.'), ), ), )); return parent::beforeValidate($options); } /** * パーマリンクバリデーション * * @param array $check チェック値 * @return bool */ public function validPermalink($check) { $value = array_shift($check); //NGワード // 「%」「 」「#」「<」「>」「+」「\」「"」「'」「&」「?」「=」「~」「:」「;」「,」「$」「@」 // 「^/(最初にスラッシュ)」「./」「/.」「.$(最後にドット)」「^.(最初にドット)」 // 「|」「]」「[」「!」「(」「)」「*」 $pattern = '/(%| |#|<|>|\+|\\\\|\"|\'|&|\?|=|~|:|;|,|\$|@' . '|^\/|\/$|\.\/|\/\.|\.$|^\.|\||\]|\[|\!|\(|\)|\*)/'; return !(bool)preg_match($pattern, $value); } /** * パーマリンクバリデーション * * @param array $fields チェック値 * @return bool */ public function isUniquePermalink($fields) { if (!empty($this->id)) { $fields[$this->primaryKey . ' !='] = $this->id; } if (isset($this->data[$this->alias]['root_id'])) { $fields['root_id'] = $this->data[$this->alias]['root_id']; } return !$this->find('count', array('conditions' => $fields, 'recursive' => -1)); } /** * Called after each successful save operation. * * @param bool $created True if this save created a new record * @param array $options Options passed from Model::save(). * @return void * @link http://book.cakephp.org/2.0/en/models/callback-methods.html#aftersave * @see Model::save() * @throws InternalErrorException */ public function afterSave($created, $options = array()) { if (Hash::get($this->data, 'Page.id') && Hash::get($this->data, 'Page.slug') !== Current::read('Page.slug')) { $chidren = $this->children( Hash::get($this->data, 'Page.id'), false, array('Page.id', 'Page.permalink') ); $data = $this->data; foreach ($chidren as $child) { $this->id = $child[$this->alias]['id']; $pattern = '/^' . preg_quote(Current::read('Page.permalink') . '/', '/') . '/'; $permalink = preg_replace( $pattern, Hash::get($data, 'Page.permalink') . '/', $child[$this->alias]['permalink'] ); if (! $this->saveField('permalink', $permalink, array('callbacks' => false))) { throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); } } $this->data = $data; } } /** * ページの作成 * * @return array ページデータ */ public function createPage() { $this->loadModels([ 'PagesLanguage' => 'Pages.PagesLanguage', ]); $slug = 'page_' . date('YmdHis'); $result = Hash::merge( $this->create(array( 'id' => null, 'slug' => $slug, 'permalink' => $slug, 'room_id' => Current::read('Room.id'), 'root_id' => Hash::get(Current::read('Page'), 'root_id', Current::read('Page.id')), 'parent_id' => Current::read('Page.id'), )), $this->PagesLanguage->create(array( 'id' => null, 'language_id' => Current::read('Language.id'), 'name' => sprintf(__d('pages', 'New page %s'), date('YmdHis')), )) ); return $result; } /** * ページデータの存在チェック * * @param int $pageId ページID * @param int $roomId ルームID * @param int $parentRoomId 親ルームID * @return bool */ public function existPage($pageId, $roomId = null, $parentRoomId = null) { if ($roomId && $parentRoomId && $roomId !== $parentRoomId) { $result = $this->find('count', array( 'recursive' => 0, 'conditions' => array( 'Page.id' => $pageId, 'Page.room_id' => $roomId, 'Room.parent_id' => $parentRoomId, ), )); } elseif ($roomId) { $result = $this->find('count', array( 'recursive' => -1, 'conditions' => array( 'Page.id' => $pageId, 'Page.room_id' => $roomId ), )); } else { $result = $this->find('count', array( 'recursive' => -1, 'conditions' => array( 'Page.id' => $pageId, 'Page.room_id' => Current::read('Room.id') ), )); } return (bool)$result; } /** * Delete page each association model * - `atomic`: If true (default), will attempt to save all records in a single transaction. * Should be set to false if database/table does not support transactions. * * @param array $data request data * @param array $options Options to use when saving record data, See $options above. * @throws InternalErrorException * @return mixed On success Model::$data if its not empty or true, false on failure */ public function deletePage($data, $options = array()) { $this->loadModels([ 'PagesLanguage' => 'Pages.PagesLanguage', ]); $options = Hash::merge(array('atomic' => true), $options); //トランザクションBegin if ($options['atomic']) { $this->begin(); } try { //Pageの削除 if (! $this->delete($data[$this->alias]['id'])) { throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); } $conditions = array($this->PagesLanguage->alias . '.page_id' => $data[$this->alias]['id']); if (! $this->PagesLanguage->deleteAll($conditions, false)) { throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); } //Container関連の削除 $this->deleteContainers($data[$this->alias]['id']); //Box関連の削除 $this->deleteBoxes($data[$this->alias]['id']); if ($options['atomic']) { $this->commit(); } return true; } catch (Exception $ex) { if ($options['atomic']) { $this->rollback($ex); } throw $ex; } } }