diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..592d72f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - '3*' + +name: create_release + +jobs: + build: + name: create_release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Slack Notification on Start + uses: rtCamp/action-slack-notify@v2.2.0 + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_RELEASE }} + SLACK_CHANNEL: notify-nc3-release + SLACK_TITLE: "${{ github.repository }}" + SLACK_COLOR: "#f0ad4e" + SLACK_MESSAGE: "Start Job" + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + body: | + NetCommons ${{ github.ref }} released. + draft: false + prerelease: false + + # テスト成功時はこちらのステップが実行される + - name: Slack Notification on Finish + uses: rtCamp/action-slack-notify@v2.2.0 + if: success() + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_RELEASE }} + SLACK_CHANNEL: notify-nc3-release + SLACK_TITLE: "${{ github.repository }}" + SLACK_COLOR: good + SLACK_MESSAGE: "Job Success" + + # テスト失敗時はこちらのステップが実行される + - name: Slack Notification on Failure + uses: rtCamp/action-slack-notify@v2.2.0 + if: failure() + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_RELEASE }} + SLACK_CHANNEL: notify-nc3-tests + SLACK_TITLE: "${{ github.repository }}" + SLACK_COLOR: danger + SLACK_MESSAGE: "Job Failure" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..6da4321 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,168 @@ +on: + push: + branches: + - main + - master + pull_request: + branches: + - main + - master + +name: tests + +jobs: + setup: + name: setup + runs-on: ubuntu-latest + steps: + - name: Slack Notification on Start + uses: rtCamp/action-slack-notify@v2.2.0 + if: env.SLACK_WEBHOOK != '' + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_TESTS }} + SLACK_CHANNEL: notify-nc3-tests + SLACK_TITLE: "${{ github.repository }}" + SLACK_COLOR: "#f0ad4e" + + tests: + name: tests + needs: setup + runs-on: ubuntu-latest + strategy: + matrix: + php: [ '7.1', '7.2', '7.3', '7.4' ] + mysql: [ '5.7', '8.0' ] + + env: + NC3_BUILD_DIR: "/opt/nc3" + NC3_DOCKER_DIR: "/opt/docker" + NC3_GIT_URL: "git://github.com/NetCommons3/NetCommons3.git" + NC3_GIT_BRANCH: "master" + PLUGIN_BUILD_DIR: ${{ github.workspace }} + PHP_VERSION: ${{ matrix.php }} + MYSQL_VERSION: ${{ matrix.mysql }} + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: cakephp_test + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v2 + + - name: Fix up git URLs + run: echo -e '[url "https://github.com/"]\n insteadOf = "git://github.com/"' >> ~/.gitconfig + + - name: environment + run: | + echo "GITHUB_WORKSPACE=${GITHUB_WORKSPACE}" + echo "PLUGIN_BUILD_DIR=${PLUGIN_BUILD_DIR}" + echo "PHP_VERSION=${PHP_VERSION}" + echo "MYSQL_VERSION=${MYSQL_VERSION}" + ls -al ${PLUGIN_BUILD_DIR} + + - name: docker-compose install + run: | + curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` > ~/docker-compose + chmod +x ~/docker-compose + sudo mv ~/docker-compose /usr/local/bin/docker-compose + docker-compose --version + + - name: git clone nc3 + run: git clone -b ${NC3_GIT_BRANCH} ${NC3_GIT_URL} ${NC3_BUILD_DIR} + + - name: git clone nc3_docker + run: git clone https://github.com/NetCommons3/nc3app-docker.git ${NC3_DOCKER_DIR} + + - name: docker-compose start + run: | + cd ${NC3_DOCKER_DIR} + docker-compose up -d + docker-compose start + + - run: docker ps + + - name: check libraries + run: | + cd ${NC3_DOCKER_DIR} + docker-compose exec -T nc3app bash /opt/scripts/start-on-docker.sh + + - name: nc3 build + run: | + cd ${NC3_DOCKER_DIR} + docker-compose exec -T nc3app bash /opt/scripts/app-build.sh + + - name: phpcs (PHP CodeSniffer) + if: always() + run: | + cd ${NC3_DOCKER_DIR} + docker-compose exec -T nc3app bash /opt/scripts/phpcs.sh + + - name: phpmd (PHP Mess Detector) + if: always() + run: | + cd ${NC3_DOCKER_DIR} + docker-compose exec -T nc3app bash /opt/scripts/phpmd.sh + + - name: phpcpd (PHP Copy/Paste Detector) + if: always() + run: | + cd ${NC3_DOCKER_DIR} + docker-compose exec -T nc3app bash /opt/scripts/phpcpd.sh + + - name: gjslint (JavaScript Style Check) + if: always() + run: | + cd ${NC3_DOCKER_DIR} + docker-compose exec -T nc3app bash /opt/scripts/gjslint.sh + + - name: phpdoc (PHP Documentor) + if: always() + run: | + cd ${NC3_DOCKER_DIR} + docker-compose exec -T nc3app bash /opt/scripts/phpdoc.sh + + - name: phpunit (PHP UnitTest) + if: always() + run: | + cd ${NC3_DOCKER_DIR} + docker-compose exec -T nc3app bash /opt/scripts/phpunit.sh + sudo -s chmod a+w -R ${NC3_BUILD_DIR}/build + +# - name: push coveralls +# env: +# COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# COVERALLS_FLAG_NAME: ${{ matrix.php }} +# run: | +# cd ${NC3_BUILD_DIR} +# ls -la ${NC3_BUILD_DIR} +# vendors/bin/php-coveralls --coverage_clover=build/logs/clover.xml -v + + - name: docker-compose remove + if: always() + run: | + cd ${NC3_DOCKER_DIR} + docker-compose rm -f + + # テスト失敗時はこちらのステップが実行される + - name: Slack Notification on Failure + uses: rtCamp/action-slack-notify@v2.2.0 + if: env.SLACK_WEBHOOK != '' && failure() + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_TESTS }} + SLACK_CHANNEL: notify-nc3-tests + SLACK_TITLE: "${{ github.repository }}(php${{ matrix.php }}, mysql${{ matrix.mysql }})" + SLACK_COLOR: danger + + teardown: + name: teardown + runs-on: ubuntu-latest + needs: tests + steps: + # テスト成功時はこちらのステップが実行される + - name: Slack Notification on Success + uses: rtCamp/action-slack-notify@v2.2.0 + if: env.SLACK_WEBHOOK != '' && success() + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_TESTS }} + SLACK_CHANNEL: notify-nc3-tests + SLACK_TITLE: "${{ github.repository }}" + SLACK_COLOR: good diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e231d1c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,43 +0,0 @@ -language: php - -php: - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - 7.1 - - 7.2 - -sudo: false -dist: trusty - -env: - matrix: - - NETCOMMONS_VERSION=master DB=mysql - global: - - secure: "UvZqrQzrJFGPz3z6Pbi8h5S8vEFcQT92eLLmOaapL9R+jkycRMKT2sUUKp/nM7C/TcNC/OeUULcrv/cnLH/0sb/ns6nJyoTGzOqlmViAAn92wxKVocy1kbCRYi0RWSrm8DiC492RqLk7oXhz4x8yTMHfowgw4S55M034VZVxtQg=" - - GIT_COMMITTER_NAME=s-nakajima - - GIT_COMMITTER_EMAIL=nakajimashouhei@gmail.com - - GIT_AUTHOR_NAME=s-nakajima - - GIT_AUTHOR_EMAIL=nakajimashouhei@gmail.com - -before_script: - - export NETCOMMONS_BUILD_DIR=`dirname $TRAVIS_BUILD_DIR`/NetCommons3 - - git clone git://github.com/NetCommons3/NetCommons3 $NETCOMMONS_BUILD_DIR - - cd $NETCOMMONS_BUILD_DIR - - git checkout $NETCOMMONS_VERSION - - travis_wait . tools/build/plugins/cakephp/travis/pre.sh - - . tools/build/plugins/cakephp/travis/environment.sh - -script: - - . tools/build/plugins/cakephp/travis/main.sh - -after_script: - - . tools/build/plugins/cakephp/travis/post.sh - -notifications: - email: - recipients: - - netcommons3@googlegroups.com - on_success: never # default: change - on_failure: always # default: always diff --git a/Config/Migration/1570717849_add_column_end_time.php b/Config/Migration/1570717849_add_column_end_time.php new file mode 100644 index 0000000..9e7e797 --- /dev/null +++ b/Config/Migration/1570717849_add_column_end_time.php @@ -0,0 +1,67 @@ + + * @link http://www.netcommons.org NetCommons Project + * @license http://www.netcommons.org/license.txt NetCommons License + * @copyright Copyright 2014, NetCommons Project + */ + +App::uses('NetCommonsMigration', 'NetCommons.Config/Migration'); + +/** + * ルーム削除時に不要データを削除するために使用するテーブルに終了日時を追加 + * + * @author Shohei Nakajima + * @package NetCommons\Rooms\Config\Migration + */ +class AddColumnEndTime extends CakeMigration { + +/** + * Migration description + * + * @var string + */ + public $description = 'add_column_end_time'; + +/** + * Actions to be performed + * + * @var array $migration + */ + public $migration = array( + 'up' => array( + 'create_field' => array( + 'room_delete_related_tables' => array( + 'end_time' => array('type' => 'datetime', 'null' => true, 'default' => null, 'after' => 'start_time'), + ), + ), + ), + 'down' => array( + 'drop_field' => array( + 'room_delete_related_tables' => array('end_time'), + ), + ), + ); + +/** + * Before migration callback + * + * @param string $direction Direction of migration process (up or down) + * @return bool Should process continue + */ + public function before($direction) { + return true; + } + +/** + * After migration callback + * + * @param string $direction Direction of migration process (up or down) + * @return bool Should process continue + */ + public function after($direction) { + return true; + } +} diff --git a/Config/Schema/schema.php b/Config/Schema/schema.php index 59c9052..4cbf0d7 100644 --- a/Config/Schema/schema.php +++ b/Config/Schema/schema.php @@ -92,6 +92,31 @@ public function after($event = array()) { 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB') ); +/** + * room_delete_related_tables table + * + * @var array + */ + public $room_delete_related_tables = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'unsigned' => false, 'key' => 'primary'), + 'room_id' => array('type' => 'integer', 'null' => true, 'default' => null, 'unsigned' => false, 'key' => 'index', 'comment' => 'ルームID'), + 'delete_table_name' => array('type' => 'string', 'null' => false, 'default' => null, 'collate' => 'utf8_general_ci', 'comment' => '削除するテーブル名 rooms|blokcs|pages|users|frames|roles_rooms', 'charset' => 'utf8'), + 'field_name' => array('type' => 'string', 'null' => false, 'default' => 'id', 'collate' => 'utf8_general_ci', 'comment' => 'カラム名', 'charset' => 'utf8'), + 'value' => array('type' => 'string', 'null' => true, 'default' => null, 'collate' => 'utf8_general_ci', 'comment' => '値', 'charset' => 'utf8'), + 'foreign_field_conditions' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 1200, 'collate' => 'utf8_general_ci', 'comment' => '対象とするカラムリストをJSON形式で保持 ex) {"id":["*.block_id"], "key":["*.block_key"]}', 'charset' => 'utf8'), + 'start_time' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'end_time' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'created_user' => array('type' => 'integer', 'null' => true, 'default' => null, 'unsigned' => false), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'modified_user' => array('type' => 'integer', 'null' => true, 'default' => null, 'unsigned' => false), + 'modified' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'room_id' => array('column' => 'room_id', 'unique' => 0) + ), + 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB') + ); + /** * room_role_permissions table * @@ -171,30 +196,6 @@ public function after($event = array()) { 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB') ); -/** - * room_delete_related_tables table - * - * @var array - */ - public $room_delete_related_tables = array( - 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'unsigned' => false, 'key' => 'primary'), - 'room_id' => array('type' => 'integer', 'null' => true, 'default' => null, 'unsigned' => false, 'key' => 'index', 'comment' => 'ルームID'), - 'delete_table_name' => array('type' => 'string', 'null' => false, 'default' => null, 'collate' => 'utf8_general_ci', 'comment' => '削除するテーブル名 rooms|blokcs|pages|users|frames|roles_rooms', 'charset' => 'utf8'), - 'field_name' => array('type' => 'string', 'null' => false, 'default' => 'id', 'collate' => 'utf8_general_ci', 'comment' => 'カラム名', 'charset' => 'utf8'), - 'value' => array('type' => 'string', 'null' => true, 'default' => null, 'collate' => 'utf8_general_ci', 'comment' => '値', 'charset' => 'utf8'), - 'foreign_field_conditions' => array('type' => 'string', 'null' => true, 'default' => null, 'length' => 1200, 'collate' => 'utf8_general_ci', 'comment' => '対象とするカラムリストをJSON形式で保持 ex) {"id":["*.block_id"], "key":["*.block_key"]}', 'charset' => 'utf8'), - 'start_time' => array('type' => 'datetime', 'null' => true, 'default' => null), - 'created_user' => array('type' => 'integer', 'null' => true, 'default' => null, 'unsigned' => false), - 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), - 'modified_user' => array('type' => 'integer', 'null' => true, 'default' => null, 'unsigned' => false), - 'modified' => array('type' => 'datetime', 'null' => true, 'default' => null), - 'indexes' => array( - 'PRIMARY' => array('column' => 'id', 'unique' => 1), - 'room_id' => array('column' => 'room_id', 'unique' => 0) - ), - 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB') - ); - /** * rooms_languages table * diff --git a/Console/Command/DeleteNotUsedUsersShell.php b/Console/Command/DeleteNotUsedUsersShell.php new file mode 100644 index 0000000..938627b --- /dev/null +++ b/Console/Command/DeleteNotUsedUsersShell.php @@ -0,0 +1,268 @@ + + * @author Shohei Nakajima + * @link http://www.netcommons.org NetCommons Project + * @license http://www.netcommons.org/license.txt NetCommons License + * @copyright Copyright 2014, NetCommons Project + */ + +App::uses('AppShell', 'Console/Command'); +App::uses('RoomsLibForeignConditionsParser', 'Rooms.Lib'); +App::uses('RoomsLibDeleteRoomTables', 'Rooms.Lib'); +App::uses('RoomsLibLog', 'Rooms.Lib'); +App::uses('SiteSettingUtil', 'SiteManager.Utility'); + +/** + * ルーム削除した際の関連テーブルのデータを削除する + * + * @property Room $Room + * @property RolesRoomsUser $RolesRoomsUser + * @property User $User + * @property PrivateSpace $PrivateSpace + * @property RoomDeleteRelatedTable $RoomDeleteRelatedTable + * + * @property DeleteRelatedRoomsTask $DeleteRelatedRooms + * + * @author Shohei Nakajima + * @package NetCommons\Rooms\Console\Command + */ +class DeleteNotUsedUsersShell extends AppShell { + +/** + * 使用するモデル + * + * @var array + */ + public $uses = [ + 'Rooms.Room', + 'Rooms.RolesRoomsUser', + 'Users.User', + 'PrivateSpace.PrivateSpace', + 'Rooms.RoomDeleteRelatedTable', + ]; + +/** + * 使用するタスク + * + * @var array + */ + public $tasks = [ + 'Rooms.DeleteRelatedRooms', + ]; + +/** + * 一度の処理する件数 + * + * @var int + */ + const PROCESS_COUNT = 10; + +/** + * Override startup + * + * @return void + */ + public function startup() { + $this->out(__d('rooms', 'Delete room associations Shell')); + $this->hr(); + } + +/** + * 処理実行 + * + * @return void + */ + public function main() { + //ログ出力 + RoomsLibLog::shellStartLog($this); + + //プライベートルームのデータ削除 + $result = $this->Room->query( + $this->__makeSqlDeletedPrivateRoom('COUNT(*)') + ); + $totalCount = $result[0][0]['count_num']; + $maxLoop = ceil($totalCount / self::PROCESS_COUNT); + $procCount = 0; + + for ($i = 0; $i < $maxLoop; $i++) { + //ログ出力 + RoomsLibLog::processStartLog( + $this, + sprintf('Delete Private Rooms[%s]', ($i * self::PROCESS_COUNT + 1) . '/' . $totalCount) + ); + + $records = $this->Room->query( + $this->__makeSqlDeletedPrivateRoom($this->Room->alias . '.id') + ); + foreach ($records as $record) { + //$this->out(sprintf('room_id=%s', $record[$this->Room->alias]['id']), 1, self::VERBOSE); + $this->__deleteRoom($record[$this->Room->alias]['id']); + $procCount++; + } + + //ログ出力 + RoomsLibLog::processEndLog( + $this, + sprintf('Delete Private Rooms[%s]', $procCount . '/' . $totalCount) + ); + } + + //ユーザのデータ削除 + $result = $this->User->query( + $this->__makeSqlDeletedUser('COUNT(*)') + ); + $totalCount = $result[0][0]['count_num']; + $maxLoop = ceil($totalCount / self::PROCESS_COUNT); + $procCount = 0; + + for ($i = 0; $i < $maxLoop; $i++) { + //ログ出力 + RoomsLibLog::processStartLog( + $this, + sprintf('Delete Users[%s]', ($i * self::PROCESS_COUNT + 1) . '/' . $totalCount) + ); + + $records = $this->User->query( + $this->__makeSqlDeletedUser($this->User->alias . '.id') + ); + + $this->RoomDeleteRelatedTable->begin(); + foreach ($records as $record) { + try { + //プライベートルーム削除 + //ルーム削除情報を登録する + $this->RoomDeleteRelatedTable->insertUser($record[$this->User->alias]['id'], '0'); + + //トランザクションCommit + $this->RoomDeleteRelatedTable->commit(); + + } catch (Exception $ex) { + //トランザクションRollback + $this->RoomDeleteRelatedTable->rollback($ex); + } + + $procCount++; + } + + //ログ出力 + RoomsLibLog::processEndLog( + $this, + sprintf('Delete Users[%s]', $procCount . '/' . $totalCount) + ); + } + + //関連データの削除 + $this->DeleteRelatedRooms->execute(); + + RoomsLibLog::shellEndLog($this); + } + +/** + * 既に削除されているユーザに対するプライベートルームIDを取得するSQLの生成 + * + * @param string $columnName 取得するカラム名 + * @return void + */ + private function __makeSqlDeletedPrivateRoom($columnName) { + $roomTableName = $this->Room->tablePrefix . $this->Room->table; + $roomAliasName = $this->Room->alias; + $userRoomTableName = $this->RolesRoomsUser->tablePrefix . $this->RolesRoomsUser->table; + $userRoomAliasName = $this->RolesRoomsUser->alias; + + $sqlFromWhere = "{$roomTableName} AS {$roomAliasName} " . + "LEFT JOIN {$userRoomTableName} AS {$userRoomAliasName} " . + "ON ({$roomAliasName}.id = {$userRoomAliasName}.room_id) " . + "WHERE {$roomAliasName}.space_id = " . Space::PRIVATE_SPACE_ID . " " . + "AND {$roomAliasName}.page_id_top IS NOT NULL " . + "AND {$userRoomAliasName}.id IS NULL"; + + if ($columnName === 'COUNT(*)') { + $sql = "SELECT {$columnName} count_num FROM {$sqlFromWhere}"; + } else { + $sql = "SELECT {$columnName} FROM {$sqlFromWhere} LIMIT " . self::PROCESS_COUNT; + } + + return $sql; + } + +/** + * 既に削除されているユーザに対するプライベートルームIDを取得するSQLの生成 + * + * @param string $columnName 取得するカラム名 + * @return void + */ + private function __makeSqlDeletedUser($columnName) { + $userTableName = $this->User->tablePrefix . $this->User->table; + $usetAliasName = $this->User->alias; + $delRoomTableName = $this->RoomDeleteRelatedTable->tablePrefix . + $this->RoomDeleteRelatedTable->table; + $delRoomAliasName = $this->RoomDeleteRelatedTable->alias; + + $sqlFromWhere = "{$userTableName} AS {$usetAliasName} " . + "LEFT JOIN {$delRoomTableName} AS {$delRoomAliasName} " . + "ON (" . + "{$delRoomAliasName}.delete_table_name = 'users' " . + "AND {$delRoomAliasName}.field_name = 'id' " . + "AND {$delRoomAliasName}.value = {$usetAliasName}.id" . + ") " . + "WHERE {$usetAliasName}.is_deleted = 1 " . + "AND {$delRoomAliasName}.id IS NULL"; + + if ($columnName === 'COUNT(*)') { + $sql = "SELECT {$columnName} count_num FROM {$sqlFromWhere}"; + } else { + $sql = "SELECT {$columnName} FROM {$sqlFromWhere} LIMIT " . self::PROCESS_COUNT; + } + + return $sql; + } + +/** + * ルーム削除 + * + * @param int|string $roomId ルームID + * @return void + */ + private function __deleteRoom($roomId) { + $this->RoomDeleteRelatedTable->begin(); + try { + //プライベートルーム削除 + //ルーム削除情報を登録する + $this->RoomDeleteRelatedTable->insertByRoomId($roomId); + + //roles_roomsデータ削除 + $this->Room->deleteRolesRoomByRoom($roomId); + + //frameデータの削除 + $this->Room->deleteFramesByRoom($roomId); + + //pageデータの削除 + $this->Room->deletePagesByRoom($roomId); + + //blockデータの削除 + $this->Room->deleteBlocksByRoom($roomId); + + //トランザクションCommit + //$this->RoomDeleteRelatedTable->rollback(); + $this->RoomDeleteRelatedTable->commit(); + + } catch (Exception $ex) { + //トランザクションRollback + $this->RoomDeleteRelatedTable->rollback($ex); + } + } + +/** + * 引数の使い方の取得 + * + * @return ConsoleOptionParser + */ + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description(__d('rooms', 'The Delete room associations Shell')); + } + +} diff --git a/Console/Command/DeleteRelatedRoomsShell.php b/Console/Command/DeleteRelatedRoomsShell.php new file mode 100644 index 0000000..bf96cf7 --- /dev/null +++ b/Console/Command/DeleteRelatedRoomsShell.php @@ -0,0 +1,67 @@ + + * @author Shohei Nakajima + * @link http://www.netcommons.org NetCommons Project + * @license http://www.netcommons.org/license.txt NetCommons License + * @copyright Copyright 2014, NetCommons Project + */ + +App::uses('AppShell', 'Console/Command'); +App::uses('RoomsLibLog', 'Rooms.Lib'); + +/** + * ルーム削除した際の関連テーブルのデータを削除する + * + * @property DeleteRelatedRoomsTask $DeleteRelatedRooms + * + * @author Shohei Nakajima + * @package NetCommons\Rooms\Console\Command + */ +class DeleteRelatedRoomsShell extends AppShell { + +/** + * 使用するタスク + * + * @var array + */ + public $tasks = [ + 'Rooms.DeleteRelatedRooms', + ]; + +/** + * Override startup + * + * @return void + */ + public function startup() { + $this->out(__d('rooms', 'Delete room associations Shell')); + $this->hr(); + } + +/** + * 処理実行 + * + * @return void + */ + public function main() { + RoomsLibLog::shellStartLog($this); + + $this->DeleteRelatedRooms->execute(); + + RoomsLibLog::shellEndLog($this); + } + +/** + * 引数の使い方の取得 + * + * @return ConsoleOptionParser + */ + public function getOptionParser() { + $parser = parent::getOptionParser(); + return $parser->description(__d('rooms', 'The Delete room associations Shell')); + } + +} diff --git a/Console/Command/Task/DeleteRelatedRoomsTask.php b/Console/Command/Task/DeleteRelatedRoomsTask.php new file mode 100644 index 0000000..eac0e0d --- /dev/null +++ b/Console/Command/Task/DeleteRelatedRoomsTask.php @@ -0,0 +1,141 @@ + + * @author Shohei Nakajima + * @link http://www.netcommons.org NetCommons Project + * @license http://www.netcommons.org/license.txt NetCommons License + * @copyright Copyright 2014, NetCommons Project + */ + +App::uses('AppShell', 'Console/Command'); +App::uses('RoomsLibDeleteRoomTables', 'Rooms.Lib'); +App::uses('RoomsLibLog', 'Rooms.Lib'); +App::uses('SiteSettingUtil', 'SiteManager.Utility'); + +/** + * 関連テーブルのデータを削除する + * + * @property RoomDeleteRelatedTable $RoomDeleteRelatedTable + * + * @author Shohei Nakajima + * @package NetCommons\Rooms\Console\Command + */ +class DeleteRelatedRoomsTask extends AppShell { + +/** + * 使用するモデル + * + * @var array + */ + public $uses = [ + 'Rooms.RoomDeleteRelatedTable', + ]; + +/** + * DeleteRoomTablesを操作するライブラリ + * + * @var RoomsLibDeleteRoomTables + */ + private $__RoomsLibDeleteRoomTables; + +/** + * 一度の処理する件数 + * + * @var int + */ + const PROCESS_COUNT = 100; + +/** + * タスク実行 + * + * @return void + */ + public function execute() { + $this->__RoomsLibDeleteRoomTables = + new RoomsLibDeleteRoomTables($this->RoomDeleteRelatedTable, $this); + + $tableName = $this->RoomDeleteRelatedTable->tablePrefix . + $this->RoomDeleteRelatedTable->table; + $aliasName = $this->RoomDeleteRelatedTable->alias; + + $result = $this->RoomDeleteRelatedTable->query( + "SELECT COUNT(*) count_num FROM {$tableName} AS {$aliasName}" . + " WHERE {$aliasName}.end_time IS NULL" . + //" AND {$aliasName}.room_id = 23" + //" AND {$aliasName}.id = 1420" + " FOR UPDATE" + ); + if (empty($result)) { + return $this->_stop(); + } + + $totalCount = $result[0][0]['count_num']; + $maxLoop = ceil($totalCount / self::PROCESS_COUNT); + $procCount = 0; + + for ($i = 0; $i < $maxLoop; $i++) { + //ログ出力 + RoomsLibLog::processStartLog( + $this, + sprintf('Delete Related Tables[%s]', ($i * self::PROCESS_COUNT + 1) . '/' . $totalCount) + ); + + $records = $this->RoomDeleteRelatedTable->query( + "SELECT {$aliasName}.* FROM {$tableName} AS {$aliasName}" . + " WHERE {$aliasName}.end_time IS NULL" . + //" AND {$aliasName}.room_id = 23" + //" AND {$aliasName}.id = 1420" . + " LIMIT " . self::PROCESS_COUNT + ); + + try { + foreach ($records as $record) { + //トランザクションBegin + $this->RoomDeleteRelatedTable->begin(); + + $recordId = $record['RoomDeleteRelatedTable']['id']; + $this->RoomDeleteRelatedTable->updateStartTime($recordId); + + $this->__RoomsLibDeleteRoomTables->deleteRelatedTables( + $record['RoomDeleteRelatedTable']['delete_table_name'], + $record['RoomDeleteRelatedTable']['field_name'], + $record['RoomDeleteRelatedTable']['value'], + $record['RoomDeleteRelatedTable']['foreign_field_conditions'] + ); + + $this->RoomDeleteRelatedTable->updateEndTime($recordId); + + $procCount++; + + //トランザクションCommit + $this->RoomDeleteRelatedTable->commit(); + //$this->RoomDeleteRelatedTable->rollback(); + } + + } catch (Exception $ex) { + //トランザクションRollback + $this->RoomDeleteRelatedTable->rollback($ex); + } + + //ログ出力 + RoomsLibLog::processEndLog( + $this, + sprintf('Delete Related Tables[%s]', $procCount . '/' . $totalCount) + ); + } + } + +/** + * 引数の使い方の取得 + * + * @return ConsoleOptionParser + */ + public function getOptionParser() { + $parser = parent::getOptionParser(); + + return $parser->description(__d('rooms', 'The Delete room associations Shell')); + } + +} diff --git a/Controller/Component/RoomsRolesFormComponent.php b/Controller/Component/RoomsRolesFormComponent.php index 9e52827..38b5a23 100644 --- a/Controller/Component/RoomsRolesFormComponent.php +++ b/Controller/Component/RoomsRolesFormComponent.php @@ -208,7 +208,7 @@ public function actionRoomsRolesUser(Controller $controller) { 'displayFields' => self::$displaFields, 'extra' => array( 'selectedUsers' => $controller->Session->read('RoomsRolesUsers'), - 'plugin' => $seached ? $controller->params['plugin'] : '', + 'plugin' => $controller->params['plugin'] ?? '', 'search' => $seached ) )); diff --git a/Controller/RoomAddController.php b/Controller/RoomAddController.php index 5237e93..750e1d6 100644 --- a/Controller/RoomAddController.php +++ b/Controller/RoomAddController.php @@ -165,6 +165,11 @@ public function basic() { //他言語が入力されていない場合、Currentの言語データをセット $this->SwitchLanguage->setM17nRequestValue(); + $defaultParticipation = $this->request->data['Room']['default_participation'] ?? null; + if ($this->Session->read('RoomAdd.Room.default_participation') !== $defaultParticipation) { + $this->Session->delete('RoomsRolesUsers'); + } + //登録処理 $this->request->data['Room']['in_draft'] = true; $room = $this->Room->saveRoom($this->request->data); diff --git a/Lib/RoomsLibCache.php b/Lib/RoomsLibCache.php new file mode 100644 index 0000000..d92ceea --- /dev/null +++ b/Lib/RoomsLibCache.php @@ -0,0 +1,65 @@ + + * @author Shohei Nakajima + * @link http://www.netcommons.org NetCommons Project + * @license http://www.netcommons.org/license.txt NetCommons License + * @copyright Copyright 2014, NetCommons Project + */ + +App::uses('NetCommonsCache', 'NetCommons.Utility'); + +/** + * ルーム削除時の関連テーブル削除処理に関するライブラリ + * + * @author Shohei Nakajima + * @package NetCommons\Rooms\Lib + */ +class RoomsLibCache { + +/** + * NetCommonsキャッシュオブジェクト + * + * @var NetCommonsCache + */ + private $__NetCommonsCache; + +/** + * コンストラクタ + * + * @param Model $Model モデル(当モデルは、MySQLのModelであれば何でも良い) + * @return void + */ + public function __construct(Model $Model) { + $cacheName = 'delete_rooms_' . $Model->useDbConfig; + $isTest = ($Model->useDbConfig === 'test'); + $this->__NetCommonsCache = new NetCommonsCache($cacheName, $isTest, 'netcommons_model'); + $this->__NetCommonsCache->clear(); + } + +/** + * キャッシュに登録 + * + * @param string $key キャッシュキー + * @param string $subKey キャッシュサブキー + * @param array $value キャッシュに保存する値 + * @return array + */ + public function saveCache($key, $subKey, $value) { + $this->__NetCommonsCache->write($value, $key, $subKey); + } + +/** + * カラム名に対するテーブルリストを取得する + * + * @param string $key キャッシュキー + * @param string|null $subKey キャッシュサブキー + * @return array + */ + public function readCache($key, $subKey = null) { + return $this->__NetCommonsCache->read($key, $subKey); + } + +} diff --git a/Lib/RoomsLibCommandExec.php b/Lib/RoomsLibCommandExec.php new file mode 100644 index 0000000..ad6bc9a --- /dev/null +++ b/Lib/RoomsLibCommandExec.php @@ -0,0 +1,59 @@ + + * @link http://www.netcommons.org NetCommons Project + * @license http://www.netcommons.org/license.txt NetCommons License + * @copyright Copyright 2014, NetCommons Project + */ + +/** + * コマンド実行 ライブラリ + * + * @author Shohei Nakajima + * @package NetCommons\Rooms\Lib + * @see MailSend よりコピー + */ +class RoomsLibCommandExec { + +/** + * 関連データの削除処理のシェルを実行 + * + * @return void + */ + public static function deleteRelatedRooms() { + // バックグラウンドで実行 + // コマンド例) Console/cake rooms.delete_related_rooms + self::__execInBackground(APP . 'Console' . DS . 'cake rooms.delete_related_rooms -q'); + } + +/** + * バックグラウンド実行 + * + * @param string $cmd コマンド + * @return void + */ + private static function __execInBackground($cmd) { + if (self::__isWindows()) { + // Windowsの場合 + pclose(popen('cd ' . APP . ' && start /B ' . $cmd, 'r')); + } else { + // Linuxの場合 + // logrotate問題対応 http://dqn.sakusakutto.jp/2012/08/php_exec_nohup_background.html + exec('nohup ' . $cmd . ' > /dev/null &'); + } + } + +/** + * 動作しているOS がWindows かどうかを返す。 + * + * @return bool + */ + private static function __isWindows() { + if (DIRECTORY_SEPARATOR == '\\') { + return true; + } + return false; + } +} diff --git a/Lib/RoomsLibDataSourceExecute.php b/Lib/RoomsLibDataSourceExecute.php new file mode 100644 index 0000000..42e2f3a --- /dev/null +++ b/Lib/RoomsLibDataSourceExecute.php @@ -0,0 +1,308 @@ + + * @author Shohei Nakajima + * @link http://www.netcommons.org NetCommons Project + * @license http://www.netcommons.org/license.txt NetCommons License + * @copyright Copyright 2014, NetCommons Project + */ + +App::uses('RoomsLibLog', 'Rooms.Lib'); + +/** + * ルーム削除時の関連テーブル削除処理に関するライブラリ + * + * @author Shohei Nakajima + * @package NetCommons\Rooms\Lib + */ +class RoomsLibDataSourceExecute { + +/** + * データソースオブジェクト + * + * @var DataSource + */ + private $__DataSource; + +/** + * 使用するモデル + * + * @var Model + */ + private $__Model; + +/** + * 実行シェル + * + * @var Shell + */ + private $__Shell; + +/** + * コンストラクタ + * + * @param Model $Model モデル(当モデルは、MySQLのModelであれば何でも良い) + * @param Shell|null $Shell 実行シェル + * @return void + */ + public function __construct(Model $Model, $Shell = null) { + $this->__Model = $Model; + $this->__Shell = $Shell; + $this->__DataSource = $Model->getDataSource(); + $this->__RoomsLibCache = new RoomsLibCache($Model); + } + +/** + * テーブルリストをキャッシュから取得する。なければ、最新情報から取得し、キャッシュに保存する + * + * @return array + */ + public function showTables() { + //キャッシュから取得 + $cacheTables = $this->__RoomsLibCache->readCache('tables', null); + if ($cacheTables) { + return $cacheTables; + } + + $schemaTables = $this->__findAllSchemeFileTables(); + $tablePrefix = $this->__Model->tablePrefix; + + //LIKEで多少の絞り込みを行う。ただし、tablePrefixがない場合は、全テーブルが対象となってしまう。 + $tables = $this->__Model->query("SHOW TABLES LIKE '{$tablePrefix}%s'"); + + $retTables = []; + foreach ($tables as $table) { + $realTableName = null; + if (array_key_exists('TABLE_NAMES', $table)) { + $realTableName = array_shift($table['TABLE_NAMES']); + } elseif (array_key_exists('TABLES', $table)) { + $realTableName = array_shift($table['TABLES']); + } + $realPrefix = substr($realTableName, 0, strlen($tablePrefix)); + $tableName = substr($realTableName, strlen($tablePrefix)); + + if ($tablePrefix !== $realPrefix || + ! in_array($tableName, $schemaTables)) { + continue; + } + $retTables[$tableName] = $this->showTableColumns($realTableName); + } + + //キャッシュに登録 + $this->__RoomsLibCache->saveCache('tables', null, $retTables); + + return $retTables; + } + +/** + * schemaファイルからテーブルリストを取得する + * + * @return array + */ + private function __findAllSchemeFileTables() { + App::uses('CakeSchema', 'Model'); + $plugins = App::objects('plugins'); + + $allTables = []; + foreach ($plugins as $plugin) { + $tables = $this->__getSchemeFileTablesByPlugin($plugin); + if ($tables) { + $allTables = array_merge($allTables, $tables); + } + } + + return $allTables; + } + +/** + * プラグイン名に対してschemaファイルのテーブルリストを取得する + * + * @param string $plugin プラグイン + * @return array|false + */ + private function __getSchemeFileTablesByPlugin($plugin) { + $class = $plugin . 'Schema'; + if (! CakePlugin::loaded($plugin)) { + return false; + } + $filePath = CakePlugin::path($plugin) . 'Config' . DS . 'Schema' . DS . 'schema.php'; + if (! file_exists($filePath)) { + return false; + } + include_once $filePath; + if (! class_exists($class)) { + return false; + } + $classVars = get_class_vars($class); + + $tables = []; + foreach ($classVars as $key => $value) { + if (is_array($value) && !empty($value)) { + $tables[] = $key; + } + } + + return $tables; + } + +/** + * DBからカラムリストを取得する + * + * @param string $realTableName テーブルPrefix付きの実際のテーブル名 + * @return array + */ + public function showTableColumns($realTableName) { + $columns = $this->__Model->query('SHOW COLUMNS FROM `' . $realTableName . '`'); + + $retColumns = []; + foreach ($columns as $column) { + $name = $column['COLUMNS']['Field']; + $retColumns[$name] = $column['COLUMNS']; + } + return $retColumns; + } + +/** + * SELECTのSQLを実行する + * + * @param string $tableName テーブル名 + * @param array $selectFieldNames SELECTのカラム名リスト + * @param array $wheres WHERE条件リスト + * @return array + */ + public function selectQuery($tableName, $selectFieldNames, $wheres) { + $tablePrefix = $this->__Model->tablePrefix; + $realTableName = $tablePrefix . $tableName; + $tableAlias = Inflector::classify($tableName); + + $fields = array_map(function ($fieldName) use ($tableAlias) { + return $this->__Model->escapeField($fieldName, $tableAlias); + }, $selectFieldNames); + + $sql = sprintf( + 'SELECT %s FROM `%s` AS `%s` WHERE %s', + implode(', ', $fields), + $realTableName, + $tableAlias, + $this->__makeWhereSql($tableAlias, $wheres) + ); + + $queryResults = $this->__Model->query($sql); + return $this->__flattenForDeleteTargetValues($tableName, $tableAlias, $queryResults); + } + +/** + * SELECT COUNT(*)のSQLを実行する + * + * @param string $tableName テーブル名 + * @param array $wheres WHERE条件リスト + * @return array + */ + public function countQuery($tableName, $wheres) { + $tablePrefix = $this->__Model->tablePrefix; + $realTableName = $tablePrefix . $tableName; + $tableAlias = Inflector::classify($tableName); + + $sql = sprintf( + 'SELECT COUNT(*) AS count_num FROM `%s` AS `%s` WHERE %s', + $realTableName, + $tableAlias, + $this->__makeWhereSql($tableAlias, $wheres) + ); + + $queryResults = $this->__Model->query($sql); + if (isset($queryResults[0][0]['count_num'])) { + $count = (int)$queryResults[0][0]['count_num']; + } else { + $count = 0; + } + + return $count; + } + +/** + * DELETEのSQLを実行する + * + * @param string $tableName テーブル名 + * @param array $wheres WHERE条件リスト + * @return array + */ + public function deleteQuery($tableName, $wheres) { + $tablePrefix = $this->__Model->tablePrefix; + $realTableName = $tablePrefix . $tableName; + + $sql = sprintf( + 'DELETE FROM `%s` WHERE %s', + $realTableName, + $this->__makeWhereSql($realTableName, $wheres) + ); + + RoomsLibLog::infoLog($this->__Shell, $sql, 2); + + $queryResults = $this->__Model->query($sql); + + RoomsLibLog::successLog( + $this->__Shell, + '--> AffectedRows = ' . $this->__Model->getAffectedRows(), + 2 + ); + + return $queryResults; + } + +/** + * 外部フィードキーに対して、削除対象データを取得する + * + * @param string $tableAlias テーブルのAlias名 + * @param array $wheres WHERE条件リスト + * @return array + */ + private function __makeWhereSql($tableAlias, $wheres) { + $whereConditions = []; + + foreach ($wheres as $fieldName => $values) { + $whereField = $this->__Model->escapeField($fieldName, $tableAlias); + + if (is_array($values)) { + $escapeValuesArr = array_map(function ($value) { + return $this->__DataSource->value($value, 'string'); + }, $values); + $escapeValues = implode(', ', $escapeValuesArr); + $whereConditions[] = $whereField . ' IN (' . $escapeValues . ')'; + } else { + $escapeValues = $this->__DataSource->value($values, 'string'); + $whereConditions[] = $whereField . '=' . $escapeValues; + } + } + return implode(' AND ', $whereConditions); + } + +/** + * クエリの結果をフラットの配列(一次配列、「.」で連結)にする + * + * @param string $tableName テーブル名 + * @param string $tableAlias テーブルのAlias名 + * @param array $queryResults クエリの結果 + * @return array + */ + private function __flattenForDeleteTargetValues($tableName, $tableAlias, $queryResults) { + $retResults = []; + if (empty($queryResults)) { + return $retResults; + } + + foreach ($queryResults as $result) { + foreach ($result[$tableAlias] as $field => $value) { + if (! isset($retResults[$tableName . '.' . $field])) { + $retResults[$tableName . '.' . $field] = []; + } + $retResults[$tableName . '.' . $field][] = $value; + } + } + + return $retResults; + } + +} diff --git a/Lib/RoomsLibDeleteRoomTables.php b/Lib/RoomsLibDeleteRoomTables.php new file mode 100644 index 0000000..ed885d4 --- /dev/null +++ b/Lib/RoomsLibDeleteRoomTables.php @@ -0,0 +1,436 @@ + + * @author Shohei Nakajima + * @link http://www.netcommons.org NetCommons Project + * @license http://www.netcommons.org/license.txt NetCommons License + * @copyright Copyright 2014, NetCommons Project + */ + +App::uses('RoomsLibCache', 'Rooms.Lib'); +App::uses('RoomsLibDataSourceExecute', 'Rooms.Lib'); +App::uses('RoomsLibLog', 'Rooms.Lib'); + +/** + * ルーム削除時の関連テーブル削除処理に関するライブラリ + * + * @author Shohei Nakajima + * @package NetCommons\Rooms\Lib + */ +class RoomsLibDeleteRoomTables { + +/** + * データソースオブジェクト + * + * @var DataSource + */ + private $__DataSource; + +/** + * 使用するモデル + * + * @var Model + */ + private $__Model; + +/** + * 実行シェル + * + * @var Shell + */ + private $__Shell = null; + +/** + * UploadFileモデル + * + * @var UploadFile + */ + private $__UploadFile; + +/** + * キャッシュオブジェクト + * + * @var RoomsLibCache + */ + private $__RoomsLibCache; + +/** + * キャッシュオブジェクト + * + * @var RoomsLibDataSourceExecute + */ + private $__RoomsLibDataSourceExecute; + +/** + * テーブル情報 + * + * @var array + */ + private $__tables = []; + +/** + * 除外するテーブル + * + * ※定数扱いだが、php7からconstに配列が使用できるが、php5はconstに配列が使えないためprivateのメンバー変数とする + * + * @var array + */ + private $__defIgnoreTables = [ + 'rooms', 'pages', 'blocks', 'frames', 'room_delete_related_tables', + 'roles_rooms', 'users' + ]; + +/** + * 除外する外部キー + * + * ※定数扱いだが、php7からconstに配列が使用できるが、php5はconstに配列が使えないためprivateのメンバー変数とする + * + * @var array + */ + private $__defIgnoreForeigns = [ + 'cabinet_file_trees' + ]; + +/** + * コンストラクタ + * + * @param Model $Model モデル(当モデルは、MySQLのModelであれば何でも良い) + * @param Shell|null $Shell 実行シェル + * @return void + */ + public function __construct($Model = null, $Shell = null) { + if (! $Model) { + $Model = ClassRegistry::init('Rooms.RoomDeleteRelatedTable'); + } + $this->__Model = $Model; + $this->__Shell = $Shell; + $this->__DataSource = $Model->getDataSource(); + $this->__RoomsLibCache = new RoomsLibCache($Model); + $this->__RoomsLibDataSourceExecute = new RoomsLibDataSourceExecute($Model, $Shell); + + $this->__tables = $this->__RoomsLibDataSourceExecute->showTables(); + + $this->__UploadFile = ClassRegistry::init('Files.UploadFile'); + } + +/** + * テーブルリストを取得する + * + * @return array + */ + public function getTableList() { + if ($this->__tables) { + return array_keys($this->__tables); + } else { + return []; + } + } + +/** + * カラム情報を取得する + * + * @param string $tableName テーブル名 + * @return array + */ + private function __getColumns($tableName) { + if (isset($this->__tables[$tableName])) { + return $this->__tables[$tableName]; + } else { + return []; + } + } + +/** + * テーブルに対する関連するテーブルリストを取得する + * + * @param string $tableName テーブル名 + * @param string $fieldName カラム名 + * @param string $foreignKey 外部キーフィールド名 + * @return array + */ + public function findDeleteTargetRelatedTables($tableName, $fieldName, $foreignKey) { + //キャッシュから取得 + $cacheTables = $this->__RoomsLibCache->readCache( + 'delete_related_tables', + $tableName . '_' . $fieldName . '_' . $foreignKey + ); + if ($cacheTables) { + return $cacheTables; + } + + //戻り値の関連テーブルリストを取得する + $retRelatedTables = []; + if (isset($this->__tables[$tableName][$fieldName])) { + $this->__setRecursiveReletedTables( + $tableName, $fieldName, $foreignKey, $retRelatedTables + ); + } + + //キャッシュに登録 + $this->__RoomsLibCache->saveCache( + 'delete_related_tables', + $tableName . '_' . $fieldName . '_' . $foreignKey, + $retRelatedTables + ); + + return $retRelatedTables; + } + +/** + * テーブルに対する関連するテーブルリストを再帰的に取得する + * + * @param string $tableName テーブル名 + * @param string $fieldName カラム名 + * @param string $foreignKey 外部キーフィールド名 + * @param array &$retRelatedTables 戻り値の関連テーブルリスト + * @return void + */ + private function __setRecursiveReletedTables( + $tableName, $fieldName, $foreignKey, &$retRelatedTables) { + $relatedTableNames = array_keys($this->__tables); + foreach ($relatedTableNames as $relatedTableName) { + if (in_array($relatedTableName, $this->__defIgnoreTables, true)) { + continue; + } + + $columns = $this->__getColumns($relatedTableName); + if (! isset($columns[$foreignKey])) { + continue; + } + + //戻り値の関連テーブルリストにセットする + if (! isset($retRelatedTables[$tableName][$fieldName])) { + $retRelatedTables[$tableName][$fieldName] = []; + } + $retRelatedTables[$tableName][$fieldName][] = $relatedTableName . '.' . $foreignKey; + + if (in_array($relatedTableName, $this->__defIgnoreForeigns, true)) { + continue; + } + + //idカラムがある場合、再帰的に取得する + if (isset($columns['id'])) { + $relatedForeignKey = $this->__makeForeignKey($relatedTableName, 'id'); + $this->__setRecursiveReletedTables( + $relatedTableName, 'id', $relatedForeignKey, $retRelatedTables + ); + } + //keyカラムがある場合、再帰的に取得する + if (isset($columns['key'])) { + $relatedForeignKey = $this->__makeForeignKey($relatedTableName, 'key'); + $this->__setRecursiveReletedTables( + $relatedTableName, 'key', $relatedForeignKey, $retRelatedTables + ); + } + } + + return $retRelatedTables; + } + +/** + * 外部キーのカラム名を生成する + * + * @param string $tableName テーブル名 + * @param string $fieldName カラム名 + * @return string + */ + private function __makeForeignKey($tableName, $fieldName) { + $foreignKey = Inflector::singularize($tableName) . '_' . $fieldName; + if ($tableName === 'bbses') { + $foreignKey = 'bbs_' . $fieldName; + } else { + $foreignKey = Inflector::singularize($tableName) . '_' . $fieldName; + } + return $foreignKey; + } + +/** + * 外部フィールドキーの条件からテーブルリストに展開する + * + * @param string $cacheKey キャッシュキー + * @param string $tableName テーブル名 + * @param string $forienConditions 外部フィールドの条件(変換後) + * @return array + */ + public function expandToTablesFromForienConditions($cacheKey, $tableName, $forienConditions) { + //キャッシュから取得 + $cacheTables = $this->__RoomsLibCache->readCache('expand_to_tables', $cacheKey); + if ($cacheTables) { + return $cacheTables; + } + + //外部フィールドキーの条件からテーブルリストに展開する + $retTables = []; + foreach ($forienConditions as $fieldName => $conditions) { + foreach ($conditions as $condition) { + list($relatedTableName, $foriegnKey) = explode('.', $condition); + if ($relatedTableName === '*') { + $retTables = array_merge( + $retTables, + $this->findDeleteTargetRelatedTables($tableName, $fieldName, $foriegnKey) + ); + } else { + if (! isset($retTables[$tableName][$fieldName])) { + $retTables[$tableName][$fieldName] = []; + } + $retTables[$tableName][$fieldName][] = $relatedTableName . '.' . $foriegnKey; + } + } + } + + //キャッシュに登録 + $this->__RoomsLibCache->saveCache('expand_to_tables', $cacheKey, $retTables); + + return $retTables; + } + +/** + * 関連データを削除する + * + * @param string $tableName テーブル名 + * @param string $fieldName カラム名 + * @param string $value 値 + * @param string $foreignConditions 外部フィールドの条件 + * @return void + */ + public function deleteRelatedTables($tableName, $fieldName, $value, $foreignConditions) { + $targetTableList = $this->expandToTablesFromForienConditions( + $tableName . '.' . $fieldName, + $tableName, + RoomsLibForeignConditionsParser::invertDbValue($foreignConditions) + ); + + $this->__runDeleteRecursiveRelatedTables( + $tableName, $fieldName, [$value], $targetTableList + ); + + if ($tableName === 'users') { + $this->deleteAttachment($tableName, 'avatar', $value); + } + } + +/** + * 再帰的に削除処理を実行する + * + * @param string $tableName テーブル名 + * @param string $fieldName カラム名 + * @param array $values 値 + * @param array $targetTableList 対象テーブルリスト + * @return array + */ + private function __runDeleteRecursiveRelatedTables( + $tableName, $fieldName, $values, $targetTableList) { + //テーブルリストに対象のテーブルがあれば、処理する + if (! isset($targetTableList[$tableName][$fieldName])) { + return; + } + foreach ($targetTableList[$tableName][$fieldName] as $relatedTableField) { + list($relatedTableName, $relatedFieldName) = explode('.', $relatedTableField); + if ($relatedTableName === 'upload_files') { + //アップロードファイル関連でデータ削除 + $this->__runDeleteUploadTables($relatedTableName, $relatedFieldName, $values); + } else { + if (isset($targetTableList[$relatedTableName])) { + //再帰する場合 + $results = $this->__RoomsLibDataSourceExecute->selectQuery( + $relatedTableName, + array_keys($targetTableList[$relatedTableName]), + [$relatedFieldName => $values] + ); + + if ($results) { + foreach ($results as $recursiveTableField => $recursiveValues) { + list($recursiveTableName, $recursiveFieldName) = explode('.', $recursiveTableField); + $this->__runDeleteRecursiveRelatedTables( + $recursiveTableName, + $recursiveFieldName, + $recursiveValues, + $targetTableList + ); + } + } + } + + $count = $this->__RoomsLibDataSourceExecute->countQuery( + $relatedTableName, [$relatedFieldName => $values] + ); + if ($count) { + $this->__RoomsLibDataSourceExecute->deleteQuery( + $relatedTableName, [$relatedFieldName => $values] + ); + } + } + } + } + +/** + * アップロードファイル削除処理を実行する + * + * アップロードは、物理ファイルも消さないといけないので別処理とする。 + * + * @param string $tableName テーブル名 + * @param string $fieldName カラム名 + * @param array $values 値 + * @return array + */ + private function __runDeleteUploadTables($tableName, $fieldName, $values) { + $results = $this->__RoomsLibDataSourceExecute->selectQuery( + $tableName, ['id'], [$fieldName => $values] + ); + + if (! empty($results[$tableName . '.id'])) { + $this->deleteUploadTables($results[$tableName . '.id']); + } + } + +/** + * アップロードファイル削除処理を実行する + * + * アップロードは、物理ファイルも消さないといけないので別処理とする。 + * + * @param string $tableName テーブル名 + * @param string $fieldName カラム名 + * @param int|string $contentKey コンテンツキー + * @return array + */ + public function deleteAttachment($tableName, $fieldName, $contentKey) { + $results = $this->__RoomsLibDataSourceExecute->selectQuery( + 'upload_files', + ['id'], + ['plugin_key' => $tableName, 'field_name' => $fieldName, 'content_key' => $contentKey] + ); + + if (! empty($results['upload_files.id'])) { + $this->deleteUploadTables($results['upload_files.id']); + } + } + +/** + * アップロードファイル削除処理を実行する + * + * @param array $fileIds ファイルIDリスト + * @return array + */ + public function deleteUploadTables($fileIds) { + try { + //トランザクションBegin + $this->__UploadFile->begin(); + + foreach ($fileIds as $fileId) { + RoomsLibLog::infoLog($this->__Shell, 'Delete Upload = ' . $fileId, 2); + $this->__UploadFile->deleteUploadFile($fileId); + RoomsLibLog::successLog($this->__Shell, '--> Success', 2); + } + + //トランザクションCommit + $this->__UploadFile->commit(); + + } catch (Exception $ex) { + //トランザクションRollback + $this->__UploadFile->rollback($ex); + } + } + +} diff --git a/Lib/RoomsLibForeignConditionsParser.php b/Lib/RoomsLibForeignConditionsParser.php new file mode 100644 index 0000000..0434eab --- /dev/null +++ b/Lib/RoomsLibForeignConditionsParser.php @@ -0,0 +1,88 @@ + + * @author Shohei Nakajima + * @link http://www.netcommons.org NetCommons Project + * @license http://www.netcommons.org/license.txt NetCommons License + * @copyright Copyright 2014, NetCommons Project + */ + +App::uses('RoomsLibDeleteRoomTables', 'Rooms.Lib'); + +/** + * ルーム削除時の関連テーブルのforeign_field_conditionsのパーサ処理に関するライブラリ + * + * @author Shohei Nakajima + * @package NetCommons\Rooms\Lib + */ +class RoomsLibForeignConditionsParser { + +/** + * 条件のデフォルト値 + * + * ※定数扱いだが、php7からconstに配列が使用できるが、php5はconstに配列が使えないためprivateのメンバー変数とする + * + * @var array + */ + private static $__defaultConditions = [ + 'rooms' => [ + 'id' => ['*.room_id'], + ], + 'blocks' => [ + 'id' => ['*.block_id'], + 'key' => ['*.block_key'], + ], + 'pages' => [ + 'id' => ['*.page_id'], + ], + 'users' => [ + 'id' => [ + '*.user_id', + 'calendar_event_share_users.share_user', + 'groups.created_user', + 'reservation_event_share_users.share_user' + ], + ], + 'frames' => [ + 'id' => ['*.frame_id'], + 'key' => ['*.frame_key'], + ], + ]; + +/** + * foreign_field_conditionsのデータ取得する + * + * @param string $tableName テーブル名 + * @return array + */ + public static function getForeignCondition($tableName) { + if (isset(self::$__defaultConditions[$tableName])) { + return self::$__defaultConditions[$tableName]; + } else { + return []; + } + } + +/** + * 外部フィールドのDBに登録する値に変換する + * + * @param array $value 外部フィールドの条件(変換前) + * @return string + */ + public static function convertDbValue($value) { + return json_encode($value); + } + +/** + * 外部フィールドのDBに登録するから処理できる型に変換する + * + * @param string $value 外部フィールドの条件(変換後) + * @return array + */ + public static function invertDbValue($value) { + return json_decode($value, true); + } + +} diff --git a/Lib/RoomsLibLog.php b/Lib/RoomsLibLog.php new file mode 100644 index 0000000..8537716 --- /dev/null +++ b/Lib/RoomsLibLog.php @@ -0,0 +1,219 @@ + + * @author Shohei Nakajima + * @link http://www.netcommons.org NetCommons Project + * @license http://www.netcommons.org/license.txt NetCommons License + * @copyright Copyright 2014, NetCommons Project + */ + +App::uses('Shell', 'Console/Command'); + +/** + * ルーム削除時の関連テーブル削除処理に関するライブラリ + * + * @author Shohei Nakajima + * @package NetCommons\Rooms\Lib + */ +class RoomsLibLog { + +/** + * シェル開始時間 + * + * @var float + */ + private static $__shellStartTime; + +/** + * 処理開始時間 + * + * @var float + */ + private static $__procStartTime; + +/** + * debugとしてログ出力 + * + * @param Shell|null $Shell 実行シェル + * @param string $message 出力するメッセージ + * @param int $indentLevel インデントレベル + * @return void + */ + public static function debugLog($Shell, $message, $indentLevel = 1) { + if (! empty($Shell)) { + $Shell->out( + "" . str_repeat(' ', $indentLevel) . "{$message}", + 1, + Shell::VERBOSE + ); + } + } + +/** + * infoとしてログ出力 + * + * @param Shell|null $Shell 実行シェル + * @param string $message 出力するメッセージ + * @param int $indentLevel インデントレベル + * @return void + */ + public static function infoLog($Shell, $message, $indentLevel = 1) { + if (! empty($Shell)) { + $Shell->out( + "" . str_repeat(' ', $indentLevel) . "{$message}", + 1, + Shell::VERBOSE + ); + } + } + +/** + * successとしてログ出力 + * + * @param Shell|null $Shell 実行シェル + * @param string $message 出力するメッセージ + * @param int $indentLevel インデントレベル + * @return void + */ + public static function successLog($Shell, $message, $indentLevel = 1) { + if (! empty($Shell)) { + $Shell->out( + "" . str_repeat(' ', $indentLevel) . "{$message}", + 1, + Shell::VERBOSE + ); + } + } + +/** + * シェル開始のログ出力 + * + * @param Shell|null $Shell 実行シェル + * @return void + */ + public static function shellStartLog($Shell) { + self::$__shellStartTime = microtime(true); + if (! empty($Shell)) { + $Shell->out(sprintf( + "[SHELL START %s] Memory=%s", + date('Y-m-d H:i:s'), + self::getMemoryUsage() + )); + } + } + +/** + * シェル終了のログ出力 + * + * @param Shell|null $Shell 実行シェル + * @return void + */ + public static function shellEndLog($Shell) { + $endTime = microtime(true); + if (! empty($Shell)) { + $Shell->out(sprintf( + "[SHELL E N D %s] Time=%.4f, Memory=%s", + date('Y-m-d H:i:s'), + ($endTime - self::$__shellStartTime), + self::getMemoryUsage() + )); + } + } + +/** + * 処理開始のログ出力 + * + * @param Shell|null $Shell 実行シェル + * @param string $message 出力するメッセージ + * @param int $indentLevel インデントレベル + * @return void + */ + public static function processStartLog($Shell, $message = '', $indentLevel = 1) { + self::$__procStartTime = microtime(true); + if (! empty($Shell)) { + $Shell->out(sprintf( + str_repeat(' ', $indentLevel) . "[PROCESS START %s] %s Memory=%s", + date('Y-m-d H:i:s'), + $message, + self::getMemoryUsage() + )); + } + } + +/** + * 処理終了のログ出力 + * + * @param Shell|null $Shell 実行シェル + * @param string $message 出力するメッセージ + * @param int $indentLevel インデントレベル + * @return void + */ + public static function processEndLog($Shell, $message = '', $indentLevel = 1) { + $endTime = microtime(true); + if (! empty($Shell)) { + $Shell->out(sprintf( + str_repeat(' ', $indentLevel) . "[PROCESS E N D %s] %s Time=%.4f, Memory=%s", + date('Y-m-d H:i:s'), + $message, + ($endTime - self::$__procStartTime), + self::getMemoryUsage() + )); + } + } + +/** + * メモリー使用量取得 単位付きで取得 + * + * @return string + * @see GetMemoryUsageComponent::execute() からコードをコピペ + */ + public static function getMemoryUsage() { + $size = memory_get_usage(); + return self::__formatMemory($size); + } + +/** + * getPeakMemoryUsage + * + * @return string + */ + public static function getPeakMemoryUsage() { + $size = memory_get_peak_usage(); + return self::__formatMemory($size); + } + +/** + * メモリ使用量のフォーマット + * + * @param int $size memory使用量 + * @return string + */ + private static function __formatMemory($size) { + $byte = 1024; // バイト + $mb = pow($byte, 2); // メガバイト + //$gb = pow($byte, 3); // ギガバイト + + switch(true){ + //case $size >= $gb: + // $target = $gb; + // $unit = 'GB'; + // break; + case $size >= $mb: + $target = $mb; + $unit = 'MB'; + break; + default: + $target = $byte; + $unit = 'KB'; + break; + } + + $newSize = round($size / $target, 3); + $fileSize = number_format($newSize, 3, '.', ',') . $unit; + + return $fileSize; + } + +} diff --git a/Locale/jpn/LC_MESSAGES/rooms.po b/Locale/jpn/LC_MESSAGES/rooms.po index 98dac67..7967a25 100644 --- a/Locale/jpn/LC_MESSAGES/rooms.po +++ b/Locale/jpn/LC_MESSAGES/rooms.po @@ -233,3 +233,21 @@ msgstr "%sに変更する" #: Rooms/View/Elements/Rooms/edit_form.ctp:64 msgid "Last access datetime" msgstr "最終アクセス日時" + +# +# 削除ルームに対する関連テーブル削除シェル +# +msgid "Delete room associations Shell" +msgstr "削除ルームに対する関連テーブルのデータ削除" + +msgid "Process limit" +msgstr "処理件数" + +msgid "Process room id" +msgstr "処理するルームID" + +msgid "All records" +msgstr "全レコード" + +msgid "Deleted." +msgstr "削除しました。" diff --git a/Model/Behavior/DeleteRoomAssociationsBehavior.php b/Model/Behavior/DeleteRoomAssociationsBehavior.php index 6ee84a8..8018fd2 100644 --- a/Model/Behavior/DeleteRoomAssociationsBehavior.php +++ b/Model/Behavior/DeleteRoomAssociationsBehavior.php @@ -64,18 +64,23 @@ public function deletePagesByRoom(Model $model, $roomId) { ]); //外部キーがpage_idのデータを削除 - $pageIds = $model->Page->find('list', array( + $page = $model->Page->find('first', array( 'recursive' => -1, //'fields' => array('id', 'key'), 'conditions' => array( $model->Page->alias . '.room_id' => $roomId, + 'root_id = parent_id' ), )); - $pageIds = array_keys($pageIds); + if (! $page) { + return true; // すでに対象のページがないということなので + } + + $pageId = $page['Page']['id']; - //Tree構成の関係で、ページの削除はdeleteAllでやる - if (!$model->Page->deleteAll(array($model->Page->alias . '.id' => $pageIds), false)) { - CakeLog::error('[room deleting] Page->deleteAll ' . implode(', ', $pageIds)); + //Tree構成の関係で、ページの削除はdeleteで親だけを消して、あとはCallbackに任せる + if (!$model->Page->delete(array($model->Page->alias . '.id' => $pageId), false)) { + CakeLog::error('[room deleting] Page->delete ' . $pageId); throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); } @@ -169,8 +174,13 @@ public function queryDeleteAll(Model $model, $field, $value) { $tables = $model->query("SHOW TABLES LIKE '{$prefix}%'"); foreach ($tables as $table) { - $tableName = array_shift($table['TABLE_NAMES']); - $columns = $model->query('SHOW COLUMNS FROM ' . $tableName); + $tableName = null; + if (array_key_exists('TABLE_NAMES', $table)) { + $tableName = array_shift($table['TABLE_NAMES']); + } elseif (array_key_exists('TABLES', $table)) { + $tableName = array_shift($table['TABLES']); + } + $columns = $model->query('SHOW COLUMNS FROM `' . $tableName . '`'); if (! Hash::check($columns, '{n}.COLUMNS[Field=' . $field . ']')) { continue; } diff --git a/Model/Room.php b/Model/Room.php index 32ecc64..f856be4 100644 --- a/Model/Room.php +++ b/Model/Room.php @@ -13,6 +13,7 @@ App::uses('Role', 'Roles.Model'); App::uses('Space', 'Rooms.Model'); App::uses('BlockSettingBehavior', 'Blocks.Model/Behavior'); +App::uses('RoomsLibCommandExec', 'Rooms.Lib'); /** * Room Model @@ -624,10 +625,12 @@ public function saveMove($room, $moveMethod, $moveStep = 1) { * ルームの削除処理 * * @param array $data received post data + * @param bool $execDelCommand 削除コマンドを実行するかどうか * @return bool True on success, false on validation errors * @throws InternalErrorException + * @SuppressWarnings(PHPMD.BooleanArgumentFlag) */ - public function deleteRoom($data) { + public function deleteRoom($data, $execDelCommand = true) { $this->loadModels([ 'RoomsLanguage' => 'Rooms.RoomsLanguage', ]); @@ -644,6 +647,12 @@ public function deleteRoom($data) { //トランザクションCommit $this->commit(); + //コマンドライン実行 + if ($execDelCommand) { + //会員の一括削除の場合、毎回実行しないようにするため。 + RoomsLibCommandExec::deleteRelatedRooms(); + } + } catch (Exception $ex) { //トランザクションRollback $this->rollback($ex); diff --git a/Model/RoomDeleteRelatedTable.php b/Model/RoomDeleteRelatedTable.php index e3572f5..3601abe 100644 --- a/Model/RoomDeleteRelatedTable.php +++ b/Model/RoomDeleteRelatedTable.php @@ -10,6 +10,7 @@ */ App::uses('RoomsAppModel', 'Rooms.Model'); +App::uses('RoomsLibForeignConditionsParser', 'Rooms.Lib'); /** * ルーム削除時に関連して削除するテーブル Model @@ -19,36 +20,6 @@ */ class RoomDeleteRelatedTable extends RoomsAppModel { -/** - * 削除 - * - * @var array - */ - private $__defaultConditions = [ - 'rooms' => [ - 'id' => ['*.room_id'], - ], - 'blocks' => [ - 'id' => ['*.block_id'], - 'key' => ['*.block_key'], - ], - 'pages' => [ - 'id' => ['*.page_id'], - ], - 'users' => [ - 'id' => [ - '*.user_id', - 'calendar_event_share_users.share_user', - 'groups.created_user', - 'reservation_event_share_users.share_user' - ], - ], - 'frames' => [ - 'id' => ['*.frame_id'], - 'key' => ['*.frame_key'], - ], - ]; - /** * ルームIDを基にルーム削除関連データを追加 * @@ -63,6 +34,10 @@ public function insertByRoomId($roomId) { 'Block' => 'Blocks.Block', ]); + if ($this->__existTableValue($this->Room->table, 'id', $roomId)) { + return; + } + $targetTables = [ //$this->Room->table, $this->Page->table, @@ -76,13 +51,17 @@ public function insertByRoomId($roomId) { try { $table = $this->Room->table; $this->__execInsertQuery( - $roomId, 'id', $roomId, $table, 'id', $this->__defaultConditions[$table] + $roomId, 'id', $roomId, $table, 'id', + RoomsLibForeignConditionsParser::getForeignCondition($table) ); foreach ($targetTables as $table) { - $this->__execInsertQuery( - $roomId, 'room_id', $roomId, $table, 'id', $this->__defaultConditions[$table] - ); + $foreignConditions = RoomsLibForeignConditionsParser::getForeignCondition($table); + foreach ($foreignConditions as $field => $condition) { + $this->__execInsertQuery( + $roomId, 'room_id', $roomId, $table, $field, [$field => $condition] + ); + } } //トランザクションCommit @@ -106,13 +85,37 @@ public function insertUser($userId, $roomId) { 'User' => 'Users.User', ]); + $table = $this->User->table; + $this->insert( + $roomId, $table, 'id', $userId, + RoomsLibForeignConditionsParser::getForeignCondition($table) + ); + } + +/** + * ルーム削除関連データを追加 + * + * insertByRoomId()やinsertUser()に含まれないものを追加する時に使用する + * + * @param int|string $roomId ルームID + * @param string $tableName テーブル名 + * @param string $fieldName カラム名 + * @param string|int $value 値 + * @param array $foreignConditions 外部キーリスト + * @return void + */ + public function insert($roomId, $tableName, $fieldName, $value, $foreignConditions) { + //既に登録されているかチェック + if ($this->__existTableValue($tableName, $fieldName, $value)) { + return; + } + //トランザクションBegin $this->begin(); try { - $table = $this->User->table; $this->__execInsertQuery( - $roomId, 'id', $userId, $table, 'id', $this->__defaultConditions[$table] + $roomId, $fieldName, $value, $tableName, $fieldName, $foreignConditions ); //トランザクションCommit @@ -124,6 +127,27 @@ public function insertUser($userId, $roomId) { } } +/** + * 対象テーブルの値が存在するか否か + * + * @param string $tableName 対象テーブル名 + * @param string $fieldName 対象カラム名 + * @param string $value 対象の値 + * + * @return bool + */ + private function __existTableValue($tableName, $fieldName, $value) { + $findTableName = $this->tablePrefix . $this->table; + $result = $this->query( + "SELECT COUNT(*) count_num FROM {$findTableName} AS {$this->alias}" . + " WHERE {$this->alias}.delete_table_name = :tableName" . + " AND {$this->alias}.field_name = :fieldName" . + " AND {$this->alias}.value = :value", + ['tableName' => $tableName, 'fieldName' => $fieldName, 'value' => $value] + ); + return $result[0][0]['count_num'] > 0; + } + /** * ルーム削除に関する情報を追加する * @@ -150,7 +174,8 @@ private function __execInsertQuery( 'delete_table_name' => $db->value($targetTableName, 'string'), 'field_name' => $db->value($targetFieldName, 'string'), 'value' => $this->escapeField($targetFieldName, $targetAlias), - 'foreign_field_conditions' => $db->value(json_encode($foreignConditions), 'string'), + 'foreign_field_conditions' => + $db->value(RoomsLibForeignConditionsParser::convertDbValue($foreignConditions), 'string'), 'created' => $db->value(date('Y-m-d H:i:s'), 'string'), 'created_user' => $db->value($loginUserId, 'string'), 'modified' => $db->value(date('Y-m-d H:i:s'), 'string'), @@ -161,10 +186,60 @@ private function __execInsertQuery( ' (' . implode(', ', array_keys($values)) . ')' . ' SELECT ' . implode(', ', $values) . ' FROM ' . $fullTargetTableName . ' AS ' . $targetAlias . - ' WHERE ' . $this->escapeField($findField, $targetAlias) . ' = ' . $findValue; + ' WHERE ' . $this->escapeField($findField, $targetAlias) . + ' = ' . $db->value($findValue, 'string'); //登録処理 return $this->query($sql); } +/** + * 開始時間の更新 + * + * @param int|string $id ID + * @return bool + */ + public function updateStartTime($id) { + $this->__updateField($id, 'start_time', gmdate('Y-m-d H:i:s')); + } + +/** + * 終了日時の更新 + * + * @param int|string $id ID + * @return bool + */ + public function updateEndTime($id) { + $this->__updateField($id, 'end_time', gmdate('Y-m-d H:i:s')); + } + +/** + * 更新処理 + * + * @param int|string $id ID + * @param string $name カラム名 + * @param string $value 値 + * @return bool|array See Model::save() False on failure or an array of model data on success. + * @see Model::save() + * @link https://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savefield-string-fieldname-string-fieldvalue-validate-false + */ + private function __updateField($id, $name, $value) { + //トランザクションBegin + $this->begin(); + + try { + $this->create(false); + + $options = ['validate' => false, 'fieldList' => [$name]]; + $this->save([$this->alias => [$this->primaryKey => $id, $name => $value]], $options); + + //トランザクションCommit + $this->commit(); + + } catch (Exception $ex) { + //トランザクションRollback + $this->rollback($ex); + } + } + } diff --git a/README.md b/README.md index 57e329c..dccdb4b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,6 @@ Rooms ===== -Rooms for NetComomns3 - -[![Build Status](https://api.travis-ci.org/NetCommons3/Rooms.png?branch=master)](https://travis-ci.org/NetCommons3/Rooms) -[![Coverage Status](https://coveralls.io/repos/NetCommons3/Rooms/badge.png?branch=master)](https://coveralls.io/r/NetCommons3/Rooms?branch=master) - -| dependencies | status | -| ------------ | ------ | -| composer.json | [![Dependency Status](https://www.versioneye.com/user/projects/53f823dde09da395fc0004e2/badge.png)](https://www.versioneye.com/user/projects/53f823dde09da395fc0004e2) | \ No newline at end of file +[![Tests Status](https://github.com/NetCommons3/Rooms/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/NetCommons3/Rooms/actions/workflows/tests.yml) +[![Coverage Status](https://coveralls.io/repos/NetCommons3/Rooms/badge.svg?branch=master)](https://coveralls.io/r/NetCommons3/Rooms?branch=master) +[![Stable Version](https://img.shields.io/packagist/v/netcommons/rooms.svg?label=stable)](https://packagist.org/packages/netcommons/rooms) diff --git a/Test/Case/Model/Behavior/DeleteRoomAssociationsBehavior/DeletePagesByRoomTest.php b/Test/Case/Model/Behavior/DeleteRoomAssociationsBehavior/DeletePagesByRoomTest.php index 60ea29f..004d751 100644 --- a/Test/Case/Model/Behavior/DeleteRoomAssociationsBehavior/DeletePagesByRoomTest.php +++ b/Test/Case/Model/Behavior/DeleteRoomAssociationsBehavior/DeletePagesByRoomTest.php @@ -93,7 +93,7 @@ public function testDeletePagesByRoom($roomId) { * @return void */ public function testDeletePagesByRoomOnExceptionError($roomId) { - $this->_mockForReturnFalse('TestModel', 'Pages.Page', 'deleteAll'); + $this->_mockForReturnFalse('TestModel', 'Pages.Page', 'delete'); //事前チェック $this->__assertTable('Page', 3); @@ -102,7 +102,6 @@ public function testDeletePagesByRoomOnExceptionError($roomId) { $this->setExpectedException('InternalErrorException'); $this->TestModel->deletePagesByRoom($roomId); } - /** * テーブルのチェック * diff --git a/Test/Case/Model/Behavior/SaveRoomAssociationsBehavior/SaveDefaultPageTest.php b/Test/Case/Model/Behavior/SaveRoomAssociationsBehavior/SaveDefaultPageTest.php index e2ccdad..1deac3d 100644 --- a/Test/Case/Model/Behavior/SaveRoomAssociationsBehavior/SaveDefaultPageTest.php +++ b/Test/Case/Model/Behavior/SaveRoomAssociationsBehavior/SaveDefaultPageTest.php @@ -113,7 +113,7 @@ public function testPageOnExceptionError($data) { * @return void */ public function testRoomOnExceptionError($data) { - $this->_mockForReturnTrue('TestModel', 'Pages.Page', 'savePage'); + $this->_mockForReturn('TestModel', 'Pages.Page', 'savePage', ['Page' => ['id' => '5']]); $this->_mockForReturnFalse('TestModel', 'Rooms.Room', 'updateAll'); //テスト実施 diff --git a/Test/Case/Model/RolesRoomsUser/GetRolesRoomsUsersTest.php b/Test/Case/Model/RolesRoomsUser/GetRolesRoomsUsersTest.php index a8b60be..bc60f63 100644 --- a/Test/Case/Model/RolesRoomsUser/GetRolesRoomsUsersTest.php +++ b/Test/Case/Model/RolesRoomsUser/GetRolesRoomsUsersTest.php @@ -73,7 +73,7 @@ public function testGetRolesRoomsUsers() { ); //テスト実施 - $result = $this->$model->$methodName($conditions); + $result = $this->$model->$methodName($conditions, ['order' => ['Room.id' => 'asc']]); //チェック $this->assertCount(3, $result); diff --git a/Test/Fixture/Page4deleteFixture.php b/Test/Fixture/Page4deleteFixture.php index 544a114..0dd3c7c 100644 --- a/Test/Fixture/Page4deleteFixture.php +++ b/Test/Fixture/Page4deleteFixture.php @@ -43,7 +43,8 @@ class Page4deleteFixture extends PageFixture { array( 'id' => '1', 'room_id' => '2', - 'parent_id' => null, + 'parent_id' => 1, + 'root_id' => 1, //'lft' => '1', //'rght' => '2', 'weight' => '1', @@ -57,12 +58,13 @@ class Page4deleteFixture extends PageFixture { array( 'id' => '2', 'room_id' => '5', - 'parent_id' => null, + 'parent_id' => 1, + 'root_id' => 1, //'lft' => '3', //'rght' => '4', 'weight' => '2', 'sort_key' => '~00000002', - 'child_count' => '0', + 'child_count' => '1', 'permalink' => 'delete_page_1', 'slug' => 'delete_page_1', 'is_container_fluid' => true, @@ -70,11 +72,12 @@ class Page4deleteFixture extends PageFixture { array( 'id' => '3', 'room_id' => '5', - 'parent_id' => null, + 'parent_id' => 2, + 'root_id' => 1, //'lft' => '5', //'rght' => '6', - 'weight' => '3', - 'sort_key' => '~00000003', + 'weight' => '1', + 'sort_key' => '~00000002-00000001', 'child_count' => '0', 'permalink' => 'delete_page_2', 'slug' => 'delete_page_2', diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 0000000..86fb650 --- /dev/null +++ b/VERSION.txt @@ -0,0 +1 @@ +3.3.7 diff --git a/View/Helper/RoomsHelper.php b/View/Helper/RoomsHelper.php index 2d91ec6..05f5ab6 100644 --- a/View/Helper/RoomsHelper.php +++ b/View/Helper/RoomsHelper.php @@ -322,7 +322,7 @@ public function roomRoleName($roomRoleKey, $options = []) { ' data-content="' . $roleDesc . '"' . //' data-trigger="focus">'; ' data-trigger="focus">'; - $html .= ''; + $html .= ''; $html .= ''; $html .= ''; diff --git a/View/Helper/RoomsRolesFormHelper.php b/View/Helper/RoomsRolesFormHelper.php index 4126234..2a086f4 100644 --- a/View/Helper/RoomsRolesFormHelper.php +++ b/View/Helper/RoomsRolesFormHelper.php @@ -196,7 +196,7 @@ public function roomRolesDescription($options = []) { ' data-content="' . h($description) . '"' . ' data-trigger="focus">'; - $html .= ''; + $html .= ''; $html .= ''; $html .= ''; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 31a3436..b6cfc6d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,8 @@ + + + app/Plugin/Rooms @@ -14,5 +17,6 @@ +