<?php
    /*********************************************************************************
     * Zurmo is a customer relationship management program developed by
     * Zurmo, Inc. Copyright (C) 2017 Zurmo Inc.
     *
     * Zurmo is free software; you can redistribute it and/or modify it under
     * the terms of the GNU Affero General Public License version 3 as published by the
     * Free Software Foundation with the addition of the following permission added
     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
     *
     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
     * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
     * details.
     *
     * You should have received a copy of the GNU Affero General Public License along with
     * this program; if not, see http://www.gnu.org/licenses or write to the Free
     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
     * 02110-1301 USA.
     *
     * You can contact Zurmo, Inc. with a mailing address at 27 North Wacker Drive
     * Suite 370 Chicago, IL 60606. or at email address contact@zurmo.com.
     *
     * The interactive user interfaces in original and modified versions
     * of this program must display Appropriate Legal Notices, as required under
     * Section 5 of the GNU Affero General Public License version 3.
     *
     * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
     * these Appropriate Legal Notices must retain the display of the Zurmo
     * logo and Zurmo copyright notice. If the display of the logo is not reasonably
     * feasible for technical reasons, the Appropriate Legal Notices must display the words
     * "Copyright Zurmo Inc. 2017. All rights reserved".
     ********************************************************************************/

    class OwnedSecurableItemReadPermissionOptimizationTest extends ZurmoBaseTest
    {
        public static function setUpBeforeClass()
        {
            parent::setUpBeforeClass();
            ZurmoDatabaseCompatibilityUtil::dropStoredFunctionsAndProcedures();
            SecurityTestHelper::createSuperAdmin();
            SecurityTestHelper::createUsers();
            SecurityTestHelper::createGroups();
            SecurityTestHelper::createRoles();
            RedBeanModel::forgetAll();
            //do the rebuild to ensure the tables get created properly.
            ReadPermissionsOptimizationUtil::rebuild();
            //Manually build the test model munge tables.
            ReadPermissionsOptimizationUtil::recreateTable(ReadPermissionsOptimizationUtil::getMungeTableName('OwnedSecurableTestItem'));
            ReadPermissionsOptimizationUtil::recreateTable(ReadPermissionsOptimizationUtil::getMungeTableName('OwnedSecurableTestItem2'));

            $benny = User::getByUsername('benny');

            $model = new OwnedSecurableTestItem();
            $model->member = 'test';
            $saved = $model->save();
            assert('$saved'); // Not Coding Standard
            $model = new OwnedSecurableTestItem();
            $model->member = 'test2';
            $saved = $model->save();
            assert('$saved'); // Not Coding Standard
            $model = new OwnedSecurableTestItem();
            $model->member = 'test3';
            $model->owner  = $benny;
            $saved = $model->save();
            assert('$saved'); // Not Coding Standard
            assert(OwnedSecurableTestItem::getCount() == 3); // Not Coding Standard
            $model = new OwnedSecurableTestItem2();
            $model->member = 'test5';
            $saved = $model->save();
            assert('$saved'); // Not Coding Standard
        }

        public static function getDependentTestModelClassNames()
        {
            return array('OwnedSecurableTestItem', 'OwnedSecurableTestItem2');
        }

        public function setUp()
        {
            parent::setUp();
            Yii::app()->user->userModel = User::getByUsername('super');
        }

        public function testSuperAdminUserGetAllMakeSubsetOrCountSqlQuery()
        {
            $quote = DatabaseCompatibilityUtil::getQuote();
            //Query should not have distinct.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $subsetSql = OwnedSecurableTestItem::makeSubsetOrCountSqlQuery('ownedsecurabletestitem',
                                                    $joinTablesAdapter, 1, 5, null, null);
            $compareSubsetSql  = "select {$quote}ownedsecurabletestitem{$quote}.{$quote}id{$quote} id ";
            $compareSubsetSql .= "from {$quote}ownedsecurabletestitem{$quote} ";
            $compareSubsetSql .= ' limit 5 offset 1';
            $this->assertEquals($compareSubsetSql, $subsetSql);
            //Make sure the sql runs properly.
            $data = OwnedSecurableTestItem::getSubset($joinTablesAdapter, 0, 5, null, null);
            $this->assertEquals(3, count($data));
            //Check using the getCount method produces the same results.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $this->assertEquals(3, OwnedSecurableTestItem::getCount($joinTablesAdapter));
        }

        public function testMungeForRegularUserMakeSubsetOrCountSqlQuery()
        {
            $quote = DatabaseCompatibilityUtil::getQuote();
            //regular user no elevation
            $benny = User::getByUsername('benny');
            Yii::app()->user->userModel = $benny;
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $mungeIds = AllPermissionsOptimizationUtil::getMungeIdsByUser($benny);
            $subsetSql = OwnedSecurableTestItem::makeSubsetOrCountSqlQuery('ownedsecurabletestitem',
                                                    $joinTablesAdapter, 1, 5, null, null);
            $compareSubsetSql  = "select distinct {$quote}ownedsecurabletestitem{$quote}.{$quote}id{$quote} id ";
            $compareSubsetSql .= "from ({$quote}ownedsecurabletestitem{$quote}, {$quote}ownedsecurableitem{$quote}) ";
            $compareSubsetSql .= "left join {$quote}ownedsecurabletestitem_read{$quote} on ";
            $compareSubsetSql .= "{$quote}ownedsecurabletestitem_read{$quote}.{$quote}securableitem_id{$quote} = ";
            $compareSubsetSql .= "{$quote}ownedsecurableitem{$quote}.{$quote}securableitem_id{$quote} ";
            $compareSubsetSql .= "and {$quote}munge_id{$quote} in ('" . join("', '", $mungeIds) . "') ";
            $compareSubsetSql .= "where ({$quote}ownedsecurableitem{$quote}.{$quote}owner__user_id{$quote} = $benny->id ";
            $compareSubsetSql .= "OR {$quote}ownedsecurabletestitem_read{$quote}.{$quote}munge_id{$quote} IS NOT NULL) ";  // Not Coding Standard
            $compareSubsetSql .= "and {$quote}ownedsecurableitem{$quote}.{$quote}id{$quote} = ";
            $compareSubsetSql .= "{$quote}ownedsecurabletestitem{$quote}.{$quote}ownedsecurableitem_id{$quote}";
            $compareSubsetSql .= ' limit 5 offset 1';
            $this->assertEquals($compareSubsetSql, $subsetSql);
            //Make sure the sql runs properly.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $data = OwnedSecurableTestItem::getSubset($joinTablesAdapter, 0, 5, null, null);
            $this->assertEquals(1, count($data));
            $this->assertTrue($data[0]->owner->isSame($benny));
            //Check using the getCount method produces the same results.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $this->assertEquals(1, OwnedSecurableTestItem::getCount($joinTablesAdapter));

            //Add in a where clause and make sure the query still works.
            $metadataAdapter = new SearchDataProviderMetadataAdapter(new OwnedSecurableTestItem(), $benny->id, array('member' => 'test'));
            $searchAttributeData = $metadataAdapter->getAdaptedMetadata();
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $where = RedBeanModelDataProvider::makeWhere('OwnedSecurableTestItem', $searchAttributeData, $joinTablesAdapter);
            $compareWhere = "({$quote}ownedsecurabletestitem{$quote}.{$quote}member{$quote} like 'test%')";
            $this->assertEquals($compareWhere, $where);
            $subsetSql = OwnedSecurableTestItem::makeSubsetOrCountSqlQuery('ownedsecurabletestitem',
                                                    $joinTablesAdapter, 1, 5, $where, null);
            $compareSubsetSql  = "select distinct {$quote}ownedsecurabletestitem{$quote}.{$quote}id{$quote} id ";
            $compareSubsetSql .= "from ({$quote}ownedsecurabletestitem{$quote}, {$quote}ownedsecurableitem{$quote}) ";
            $compareSubsetSql .= "left join {$quote}ownedsecurabletestitem_read{$quote} on ";
            $compareSubsetSql .= "{$quote}ownedsecurabletestitem_read{$quote}.{$quote}securableitem_id{$quote} = ";
            $compareSubsetSql .= "{$quote}ownedsecurableitem{$quote}.{$quote}securableitem_id{$quote} ";
            $compareSubsetSql .= "and {$quote}munge_id{$quote} in ('" . join("', '", $mungeIds) . "') ";
            $compareSubsetSql .= "where ($compareWhere) and ({$quote}ownedsecurableitem{$quote}.{$quote}owner__user_id{$quote} = $benny->id ";
            $compareSubsetSql .= "OR {$quote}ownedsecurabletestitem_read{$quote}.{$quote}munge_id{$quote} IS NOT NULL) "; // Not Coding Standard
            $compareSubsetSql .= "and {$quote}ownedsecurableitem{$quote}.{$quote}id{$quote} = ";
            $compareSubsetSql .= "{$quote}ownedsecurabletestitem{$quote}.{$quote}ownedsecurableitem_id{$quote}";
            $compareSubsetSql .= ' limit 5 offset 1';
            $this->assertEquals($compareSubsetSql, $subsetSql);
            //now run query to make sure it actually works.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $where = RedBeanModelDataProvider::makeWhere('OwnedSecurableTestItem', $searchAttributeData, $joinTablesAdapter);
            $data = OwnedSecurableTestItem::getSubset($joinTablesAdapter, 0, 5, null, null);
            $this->assertEquals(1, count($data));
            $this->assertTrue($data[0]->owner->isSame($benny));
            //Check using the getCount method produces the same results.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            RedBeanModelDataProvider::makeWhere('OwnedSecurableTestItem', $searchAttributeData, $joinTablesAdapter);
            $this->assertEquals(1, OwnedSecurableTestItem::getCount($joinTablesAdapter));

            //Now give Benny READ on the module. After that he should not need the munge query.
            Yii::app()->user->userModel = User::getByUsername('super');
            $item       = NamedSecurableItem::getByName('ZurmoModule');
            $item->addPermissions($benny, Permission::READ);
            $this->assertTrue($item->save());
            Yii::app()->user->userModel = User::getByUsername('benny');

            //There should not be a 'distinct' in the query or the munge part.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $subsetSql = OwnedSecurableTestItem::makeSubsetOrCountSqlQuery('ownedsecurabletestitem',
                                                    $joinTablesAdapter, 1, 5, null, null);
            $compareSubsetSql  = "select {$quote}ownedsecurabletestitem{$quote}.{$quote}id{$quote} id ";
            $compareSubsetSql .= "from {$quote}ownedsecurabletestitem{$quote} ";
            $compareSubsetSql .= ' limit 5 offset 1';
            $this->assertEquals($compareSubsetSql, $subsetSql);
            //Make sure the sql runs properly.
            $data = OwnedSecurableTestItem::getSubset($joinTablesAdapter, 0, 5, null, null);
            $this->assertEquals(3, count($data));
            //Check using the getCount method produces the same results.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $this->assertEquals(3, OwnedSecurableTestItem::getCount($joinTablesAdapter));

           //make sure adding a where query is ok without any munge query parts
            $metadataAdapter = new SearchDataProviderMetadataAdapter(new OwnedSecurableTestItem(), $benny->id, array('member' => 'test'));
            $searchAttributeData = $metadataAdapter->getAdaptedMetadata();
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $where = RedBeanModelDataProvider::makeWhere('OwnedSecurableTestItem', $searchAttributeData, $joinTablesAdapter);
            $compareWhere = "({$quote}ownedsecurabletestitem{$quote}.{$quote}member{$quote} like 'test%')";
            $this->assertEquals($compareWhere, $where);
            $subsetSql = OwnedSecurableTestItem::makeSubsetOrCountSqlQuery('ownedsecurabletestitem',
                                                    $joinTablesAdapter, 1, 5, $where, null);
            $compareSubsetSql  = "select {$quote}ownedsecurabletestitem{$quote}.{$quote}id{$quote} id ";
            $compareSubsetSql .= "from {$quote}ownedsecurabletestitem{$quote} ";
            $compareSubsetSql .= "where $compareWhere";
            $compareSubsetSql .= ' limit 5 offset 1';
            $this->assertEquals($compareSubsetSql, $subsetSql);
            //now run query to make sure it actually works.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            RedBeanModelDataProvider::makeWhere('OwnedSecurableTestItem', $searchAttributeData, $joinTablesAdapter);
            $data = OwnedSecurableTestItem::getSubset($joinTablesAdapter, 0, 5, null, null);
            $this->assertEquals(3, count($data));
            //Check using the getCount method produces the same results.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $where = RedBeanModelDataProvider::makeWhere('OwnedSecurableTestItem', $searchAttributeData, $joinTablesAdapter);
            $this->assertEquals(3, OwnedSecurableTestItem::getCount($joinTablesAdapter));

            //Now remove READ ALLOW for BENNY, and Add READ DENY
            Yii::app()->user->userModel = User::getByUsername('super');
            $item       = NamedSecurableItem::getByName('ZurmoModule');
            $item->removePermissions($benny, Permission::READ);
            $this->assertTrue($item->save());
            $item->addPermissions($benny, Permission::READ, Permission::DENY);
            $this->assertTrue($item->save());
            Yii::app()->user->userModel = User::getByUsername('benny');

            //Now confirm that the query is just on the owner only, no munge.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $subsetSql = OwnedSecurableTestItem::makeSubsetOrCountSqlQuery('ownedsecurabletestitem',
                                                    $joinTablesAdapter, 1, 5, null, null);
            $compareSubsetSql  = "select {$quote}ownedsecurabletestitem{$quote}.{$quote}id{$quote} id ";
            $compareSubsetSql .= "from ({$quote}ownedsecurabletestitem{$quote}, {$quote}ownedsecurableitem{$quote}) ";
            $compareSubsetSql .= "where {$quote}ownedsecurableitem{$quote}.{$quote}owner__user_id{$quote} = $benny->id ";
            $compareSubsetSql .= "and {$quote}ownedsecurableitem{$quote}.{$quote}id{$quote} = ";
            $compareSubsetSql .= "{$quote}ownedsecurabletestitem{$quote}.{$quote}ownedsecurableitem_id{$quote}";
            $compareSubsetSql .= ' limit 5 offset 1';
            $this->assertEquals($compareSubsetSql, $subsetSql);
            //Make sure the sql runs properly.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $data = OwnedSecurableTestItem::getSubset($joinTablesAdapter, 0, 5, null, null);
            $this->assertEquals(1, count($data));
            $this->assertTrue($data[0]->owner->isSame($benny));
            //Check using the getCount method produces the same results.
            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('OwnedSecurableTestItem');
            $this->assertEquals(1, OwnedSecurableTestItem::getCount($joinTablesAdapter));
        }

        public function testUnionQuery()
        {
            //regular user no elevation
            $user = new User();
            $user->username     = 'aaa';
            $user->firstName    = 'aaa';
            $user->lastName     = 'aaa';
            $saved = $user->save();
            $this->assertTrue($saved);
            $aUser = User::getByUsername('aaa');
            Yii::app()->user->userModel = $aUser;
            $mungeIds = AllPermissionsOptimizationUtil::getMungeIdsByUser($aUser);

            $model = new OwnedSecurableTestItem();
            $model->member = 'test4';
            $model->owner  = $aUser;
            $this->assertTrue($model->save());
            Yii::app()->user->userModel = User::getByUsername('super');
            $this->assertEquals(4, OwnedSecurableTestItem::getCount());
            $model = new OwnedSecurableTestItem2();
            $model->member = 'test4';
            $model->owner  = $aUser;
            $this->assertTrue($model->save());
            $this->assertEquals(2, OwnedSecurableTestItem2::getCount());
            Yii::app()->user->userModel = User::getByUsername('aaa');

            $quote        = DatabaseCompatibilityUtil::getQuote();

            //Test search attribute data across multiple models.
            $aFakePost = array('member' => 'test4');
            $aMetadataAdapter = new SearchDataProviderMetadataAdapter(new OwnedSecurableTestItem(false),  $aUser->id, $aFakePost);
            $bFakePost = array('member' => 'test4');
            $bMetadataAdapter = new SearchDataProviderMetadataAdapter(new OwnedSecurableTestItem2(false), $aUser->id, $bFakePost);
            $modelClassNamesAndSearchAttributeData = array(
                array('OwnedSecurableTestItem'  => $aMetadataAdapter->getAdaptedMetadata()),
                array('OwnedSecurableTestItem2' => $bMetadataAdapter->getAdaptedMetadata()),
            );
            $unionSql     = RedBeanModelsDataProvider::makeUnionSql($modelClassNamesAndSearchAttributeData,
                                                                    null, false, 2, 7);
            $compareWhere = "({$quote}ownedsecurabletestitem{$quote}.{$quote}member{$quote} like 'test4%')";
            $compareWhere2 = "({$quote}ownedsecurabletestitem2{$quote}.{$quote}member{$quote} like 'test4%')";
            $compareSubsetSql  = "(";
            $compareSubsetSql .= "select distinct {$quote}ownedsecurabletestitem{$quote}.{$quote}id{$quote} id";
            $compareSubsetSql .= ", 'OwnedSecurableTestItem' modelClassName ";
            $compareSubsetSql .= "from ({$quote}ownedsecurabletestitem{$quote}, {$quote}ownedsecurableitem{$quote}) ";
            $compareSubsetSql .= "left join {$quote}ownedsecurabletestitem_read{$quote} on ";
            $compareSubsetSql .= "{$quote}ownedsecurabletestitem_read{$quote}.{$quote}securableitem_id{$quote} = ";
            $compareSubsetSql .= "{$quote}ownedsecurableitem{$quote}.{$quote}securableitem_id{$quote} ";
            $compareSubsetSql .= "and {$quote}munge_id{$quote} in ('" . join("', '", $mungeIds) . "') ";
            $compareSubsetSql .= "where ($compareWhere) and ({$quote}ownedsecurableitem{$quote}.{$quote}owner__user_id{$quote} = $aUser->id ";
            $compareSubsetSql .= "OR {$quote}ownedsecurabletestitem_read{$quote}.{$quote}munge_id{$quote} IS NOT NULL) "; // Not Coding Standard
            $compareSubsetSql .= "and {$quote}ownedsecurableitem{$quote}.{$quote}id{$quote} = ";
            $compareSubsetSql .= "{$quote}ownedsecurabletestitem{$quote}.{$quote}ownedsecurableitem_id{$quote}";
            $compareSubsetSql .= ") ";
            $compareSubsetSql .= "UNION (";
            $compareSubsetSql .= "select distinct {$quote}ownedsecurabletestitem2{$quote}.{$quote}id{$quote} id";
            $compareSubsetSql .= ", 'OwnedSecurableTestItem2' modelClassName ";
            $compareSubsetSql .= "from ({$quote}ownedsecurabletestitem2{$quote}, {$quote}ownedsecurableitem{$quote}) ";
            $compareSubsetSql .= "left join {$quote}ownedsecurabletestitem2_read{$quote} on ";
            $compareSubsetSql .= "{$quote}ownedsecurabletestitem2_read{$quote}.{$quote}securableitem_id{$quote} = ";
            $compareSubsetSql .= "{$quote}ownedsecurableitem{$quote}.{$quote}securableitem_id{$quote} ";
            $compareSubsetSql .= "and {$quote}munge_id{$quote} in ('" . join("', '", $mungeIds) . "') ";
            $compareSubsetSql .= "where ($compareWhere2) and ({$quote}ownedsecurableitem{$quote}.{$quote}owner__user_id{$quote} = $aUser->id ";
            $compareSubsetSql .= "OR {$quote}ownedsecurabletestitem2_read{$quote}.{$quote}munge_id{$quote} IS NOT NULL) "; // Not Coding Standard
            $compareSubsetSql .= "and {$quote}ownedsecurableitem{$quote}.{$quote}id{$quote} = ";
            $compareSubsetSql .= "{$quote}ownedsecurabletestitem2{$quote}.{$quote}ownedsecurableitem_id{$quote}";
            $compareSubsetSql .= ") ";
            $compareSubsetSql .= 'limit 7 offset 2';
            $this->assertEquals($compareSubsetSql, $unionSql);

            //Make sure the sql runs properly.
            $dataProvider = new RedBeanModelsDataProvider('anId', null, false, $modelClassNamesAndSearchAttributeData);
            $data = $dataProvider->getData();
            //Test results are correct
            $this->assertEquals(2, count($data));
            $this->assertEquals('OwnedSecurableTestItem', get_class($data[0]));
            $this->assertEquals('OwnedSecurableTestItem2', get_class($data[1]));

            //Make sure union count query produces the same count.
            $this->assertEquals(2, $dataProvider->calculateTotalItemCount());
        }
    }
?>