<?php
/**
 * A dataset loader Shell. Seed files are allowed to perform any actions
 * available within a Cake environment. It's up to the seed file to inject
 * the appropraite data into the appropriate tables. This Shell just
 * provides a runtime environment for that work.
 */

namespace BasicSeed\Shell;

use Cake\Console\ConsoleOptionParser;
use Cake\Console\Shell;
use Cake\Log\Log;
use Cake\ORM\Entity;
use Cake\ORM\Table;
use Cake\Utility\Hash;

/**
 * BasicSeed Shell
 */
class BasicSeedShell extends Shell {

	/**
	 * The default "normal" seed file name.
	 *
	 * @var string
	 */
	public $seedFile = 'seed.php';

	/**
	 * The default "dev" seed file name.
	 *
	 * @var string
	 */
	public $seedDevFile = 'seed_dev.php';

	/**
	 * Set up the Shell.
	 *
	 * @return void
	 */
	public function initialize() {
		$this->_io->styles('success', ['text' => 'green']);
		// quiet = (none)/warning, white/yellow
		// out = info, cyan
		// verbose = success, green
	}

	/**
	 * Public method used for creating a new blank seed file.
	 *
	 * @return void
	 */
	public function init() {
		$path = $this->absolutePath($this->getFile());
		$this->quiet('Initializing seed file: ' . $this->shortPath($path));
		$this->existsOrCreate($path);
	}

	/**
	 * Executes the proper seed file.
	 *
	 * Initializes an empty seed file first if it does not already exist.
	 *
	 * @return int|void
	 */
	public function main() {
		$this->includeFile($this->absolutePath($this->getFile()));
		$this->quiet("...Done!");
	}

	/**
	 * Helper method for seeds to use when loading data from many tables.
	 *
	 * Will create or update records as necessary to make the database contain
	 * *at least* what is specified in this Seed. (Although any additional
	 * records will remain untouched unless the `_truncate` key is present and
	 * true.)
	 *
	 * Provided data must be in the following format:
	 *
	 * 	$data = [
	 * 		'TableName' => [ // Name of the table to import.
	 * 			//'_truncate' => true, // Will remove all existing records when present and true.
	 * 			//'_options' => [ // Define options to pass to newEntity(). Allows for disabling validation
	 * 			//	'validate' => false,
	 * 			//],
	 * 			'_defaults' => [ // Define default values for fields.
	 * 				'author' => 'Jane Doe',
	 * 				'is_published' => true,
	 * 			],
	 * 			[ // Each record must only define *unique* fields.
	 * 				'id' => 1,
	 * 				'title' => 'Frequently Asked Questions',
	 * 				'body' => 'Lorum ipsum.',
	 * 			],
	 * 			[
	 * 				'id' => 2,
	 * 				'title' => 'About Us',
	 * 				'body' => 'Bacon fillet tenderloin.',
	 * 			],
	 * 		],
	 * 	];
	 *
	 * @param array $data Array of [TableName => [ [record 1], [record 2]] sets.
	 * @return void
	 */
	public function importTables(array $data) {
		$tableCount = count($data);
		$this->out("<info>Starting seed of {$tableCount} table(s).</info>");

		foreach ($data as $table => $records) {
			$this->out("<info>{$table}</info>");

			// Set default field values.
			$defaults = [];
			if (array_key_exists('_defaults', $records)) {
				$defaults = $records['_defaults'];
				unset($records['_defaults']);
				$this->verbose("<success>{$table}: Default values set.</success>");
			}

			// Set entity options, if present.
			$entityOptions = [];
			if (array_key_exists('_options', $records)) {
				$entityOptions = $records['_options'];
				unset($records['_options']);
				$this->verbose("<success>{$table}: Entity options set, but...</success>");
				$this->quiet("<warning>{$table}: Deprecation notice: Change [_options] to [_entityOptions].</warning>");
			} elseif (array_key_exists('_entityOptions', $records)) {
				$entityOptions = $records['_entityOptions'];
				unset($records['_entityOptions']);
				$this->verbose("<success>{$table}: Entity options set.</success>");
			}

			// Set save options, if present.
			$saveOptions = [];
			if (array_key_exists('_saveOptions', $records)) {
				$saveOptions = $records['_saveOptions'];
				unset($records['_saveOptions']);
				$this->verbose("<success>{$table}: Table save() options set.</success>");
			}

			// Truncate the table, if requested.
			$Table = $this->loadModel($table);
			if (array_key_exists('_truncate', $records) && $records['_truncate']) {
				$this->truncateTable($Table);
			}

			unset($records['_truncate']);

			// Create or update all defined records.
			$this->importTable(
				$Table,
				$this->entityGenerator($Table, $records, $defaults, $entityOptions),
				$saveOptions
			);
		}

		$this->out("<info>Seeding complete.</info>");
	}

	/**
	 * Helper generator for use with importTable().
	 *
	 * Yields a single new Entity instance approciate for $Table for each
	 * of $records where the values are merged with $defaults.
	 *
	 * Will skip any records that fail to validate, dumping validation
	 * errors to the console in the process.
	 *
	 * Used by imporTables().
	 *
	 * @param Cake\ORM\Table $Table A Table instance to save records into.
	 * @param array $records An array of Entity records to save into the Table.
	 * @param array $defaults Optional array of default field values to merge into each record.
	 * @param array $options Optional array of newEntity() options to use.
	 * @return void
	 */
	public function entityGenerator(
		Table $Table,
		array $records,
		array $defaults = [],
		array $options = []
	) {
		$defaultOptions = [
			'validate' => true,
			'accessibleFields' => ['*' => true],
		];
		$options = $options + $defaultOptions;

		$keyField = $Table->primaryKey();

		foreach ($records as $r) {
			$r = Hash::merge($defaults, $r);

			$id = (!empty($r[$keyField]) ? $r[$keyField] : false);
			if ($id) {
				$entity = $Table->find()->where([$keyField => $id])->first();
				if ($entity) {
					$entity = $Table->patchEntity($entity, $r, $options);
					if (!$entity->dirty()) {
						$this->verbose("<success>{$Table->alias()} ({$id}): No changes.</success>");
						continue;
					}

				} else {
					$entity = $Table->newEntity($r, $options);
					$entity->isNew(true);
				}

			} else {
				$entity = $Table->newEntity($r, $options);
			}

			$errors = $entity->errors();
			if ($errors) {
				$this->printValidationErrors(
					$Table->alias(),
					$id,
					$errors
				);

				continue;
			}

			yield $entity;
		}
	}

	/**
	 * Helper function to import a set of records for a single Table.
	 *
	 * Used by imporTables().
	 *
	 * @param Cake\ORM\Table $Table A Table instance to save records into.
	 * @param array|\Generator $records An array of Entity records to save into the Table.
	 * @param array $options Options to pass to save().
	 * @return void
	 */
	public function importTable(Table $Table, $records, array $options = []) {
		$defaultOptions = [
			'checkRules' => true,
			'checkExisting' => true,
		];
		$options = $options + $defaultOptions;

		foreach ($records as $record) {
			$action = ($record->isNew() ? 'Create' : 'Update');
			$result = $Table->save($record, $options);
			$key = $this->findKey($Table, $record);

			if ($result) {
				$this->verbose("<success>{$Table->alias()} ({$key}): {$action} successful.</success>");
			} else {
				$this->quiet("<warning>{$Table->alias()} ({$key}): {$action} failed.</warning>");
				$this->printValidationErrors(
					$Table->alias(),
					$this->findKey($Table, $record),
					$record->errors()
				);
			}
		}
	}

	/**
	 * Helper method that clears all records from the provided Table instance.
	 *
	 * The default file is `config/seed.php`. When the `--dev` flag is
	 * present, the default file changes to `config/seed_dev.php`. If
	 * the `--file` option is present, it overrides everything else and
	 * its value is used explicitly.
	 *
	 * @param Cake\ORM\Table $Table The Table instance to TRUNCATE.
	 * @return bool True on success truncation, false on failure.
	 */
	protected function truncateTable($Table) {
		$truncateSql = $Table->schema()->truncateSql($Table->connection())[0];
		$success = $Table->connection()->query($truncateSql);
		if ($success) {
			$this->verbose("<success>{$Table->alias()}: Existing DB records truncated.</success>");
		} else {
			$this->quiet("<warning>{$Table->alias()}: Can not truncate existing records.</warning>");
		}

		return $success;
	}

	/**
	 * Helper method to find the primary key for a record.
	 *
	 * @param Cake\ORM\Table $Table An instantiated Table object.
	 * @param Cake\ORM\ntity $entity The record being saved into the DB.
	 * @return string The numeric or UUID value of the record's primary key, or 'unknown' on failure.
	 */
	protected function findKey(Table $Table, Entity $entity) {
		if (!empty($entity->{$Table->primaryKey()})) {
			$key = $entity->{$Table->primaryKey()};
		} else {
			$key = 'unknown';
		}

		return $key;
	}

	/**
	 * Helper method to print a validation errors array in a console-readable format.
	 *
	 * @param string $table The string name of the Table.
	 * @param mixed $id The primary key for the given record.
	 * @param array $errors Validation errors array.
	 * @return void
	 */
	protected function printValidationErrors($table, $id, $errors) {
		foreach ($errors as $field => $messages) {
			foreach ((array)$messages as $message) {
				$this->quiet("<warning>{$table} ({$id}): {$field}: {$message}</warning>");
			}
		}
	}

	/**
	 * Determines which file will be executed.
	 *
	 * The default file is `config/seed.php`. When the `--dev` flag is
	 * present, the default file changes to `config/seed_dev.php`. If
	 * the `--file` option is present, it overrides everything else and
	 * its value is used explicitly.
	 *
	 * @return string The partial filename (from the /config dir) to execute.
	 */
	protected function getFile() {
		$file = ($this->params['dev'] ? $this->seedDevFile : $this->seedFile);
		if (!empty($this->params['file'])) {
			$file = $this->params['file'];
		}

		return $file;
	}

	/**
	 * Wraps around PHP's `include` to "execute" a seed file in the local scope.
	 *
	 * Seed files can do anything a Shell can do from this point, including
	 * loading Tables and saving new Entities into them. Within the seed
	 * file, the `$this` object will refer to the BasicSeedShell instance
	 * itself, providing access to `$this->loadModel()`, for example.
	 *
	 * @param string $file The full filesystem path to check/create.
	 * @return void
	 */
	protected function includeFile($file) {
		$this->quiet('Loading seed file: ' . $this->shortPath($file));
		include $file;
	}

	/**
	 * Generates a new, empty seed file if one does not already exist.
	 *
	 * This is called during initialize to ensure that the file is
	 * _always_ available for reading.
	 *
	 * @TODO: Convert this into a proper bake template.
	 *
	 * @param string $file The full filesystem path to check/create.
	 * @return void
	 */
	protected function existsOrCreate($file) {
		if (!file_exists($file)) {
			$this->out('<info>Creating empty seed file: ' . $this->shortPath($file) . '</info>');

			file_put_contents($file, <<<'EOD'
<?php
/**
 * BasicSeed plugin data seed file.
 */

namespace App\Config\BasicSeed;

use Cake\ORM\TableRegistry;

// Write your data import statements here.
$data = [
	'TableName' => [
		//'_truncate' => true,
		//'_entityOptions' => [
		//	'validate' => false,
		//],
		//'_saveOptions' => [
		//	'checkRules' => false,
		//],
		'_defaults' => [],
		[
			'id' => 1,
			'name' => 'record 1',
		],
	],
];

$this->importTables($data);

EOD
			);
		}
	}

	/**
	 * Return a fully qualified system path to the named file.
	 *
	 * Starts from the app's `/config` directory.
	 *
	 * @param string $file The base filename.
	 * @return string The full filesystem path.
	 */
	protected function absolutePath($file) {
		return ROOT . DS . 'config' . DS . $file;
	}

	/**
	 * Configure command line options and arguments.
	 *
	 * @return ConsoleOptionParser
	 */
	public function getOptionParser() {
		$parser = parent::getOptionParser();
		$parser
			->description(
				'Provides a mechanism for loading data into any of Cake\'s configured databases.'
			)
			->addSubcommand('init', [
				'help' => 'Initialize a new, empty seed file. Respects both the --dev and --file options.',
			])
			->addOption('dev', [
				'short' => 'd',
				'boolean' => true,
				'default' => false,
				'help' => 'Use the "dev" seed file instead of the default.'
			])
			->addOption('file', [
				'short' => 'f',
				'help' => 'Manually specify the file that should be used. When this option is present, its argument will always be used explicitly, overriding the --dev option if it is also present.'
			]);
		return $parser;
	}
}


<?php

namespace amilna\blog\controllers;

use Yii;
use amilna\blog\models\StaticPage;
use amilna\blog\models\StaticPageSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;

/**
 * PageController implements the CRUD actions for StaticPage model.
 */
class PageController extends Controller
{
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['post'],
                ],
            ],
        ];
    }

    /**
     * Lists all StaticPage models.
     * @params string $format, array $arraymap, string $term
     * @return mixed
     */
    public function actionIndex($format= false,$arraymap= false,$term = false)
    {
        $searchModel = new StaticPageSearch();
        $req = Yii::$app->request->queryParams;
        if ($term) { $req[basename(str_replace("\\","/",get_class($searchModel)))]["term"] = $term;}
        $dataProvider = $searchModel->search($req);

        $query = $dataProvider->query;
		$query->andWhere(['status'=>[0,1,2]]);

        if ($format == 'json')
        {
			$model = [];
			foreach ($dataProvider->getModels() as $d)
			{
				$obj = $d->attributes;
				if ($arraymap)
				{
					$map = explode(",",$arraymap);
					if (count($map) == 1)
					{
						$obj = (isset($d[$arraymap])?$d[$arraymap]:null);
					}
					else
					{
						$obj = [];
						foreach ($map as $a)
						{
							$k = explode(":",$a);
							$v = (count($k) > 1?$k[1]:$k[0]);
							$obj[$k[0]] = ($v == "Obj"?json_encode($d->attributes):(isset($d->$v)?$d->$v:null));
						}
					}
				}

				if ($term)
				{
					if (!in_array($obj,$model))
					{
						array_push($model,$obj);
					}
				}
				else
				{
					array_push($model,$obj);
				}
			}
			return \yii\helpers\Json::encode($model);
		}
		else
		{
			return $this->render('index', [
				'searchModel' => $searchModel,
				'dataProvider' => $dataProvider,
			]);
		}
    }

    /**
     * Displays a single StaticPage model.
     * @param integer $id
     * @additionalParam string $format
     * @return mixed
     */
    public function actionView($id,$format= false)
    {
        $model = $this->findModel($id);
        if ($model->status != 1)
        {
			return $this->redirect(['index']);
		}

        if ($format == 'json')
        {
			return \yii\helpers\Json::encode($model);
		}
		else
		{
			return $this->render('view', [
				'model' => $model,
			]);
		}
    }

    /**
     * Creates a new StaticPage model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new StaticPage();
        $model->time = date("Y-m-d H:i:s");
        $model->isdel = 0;

        $post = Yii::$app->request->post();
		if (isset($post['StaticPage']['tags']))
		{
			if (is_array($post['StaticPage']['tags']))
			{
				$post['StaticPage']['tags'] = implode(",",$post['StaticPage']['tags']);
			}
		}

        if ($model->load($post) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }

    /**
     * Updates an existing StaticPage model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);
		$model->tags = !empty($model->tags)?explode(",",$model->tags):[];

		$post = Yii::$app->request->post();
		if (isset($post['StaticPage']['tags']))
		{
			if (is_array($post['StaticPage']['tags']))
			{
				$post['StaticPage']['tags'] = implode(",",$post['StaticPage']['tags']);
			}
		}

        if ($model->load($post) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('update', [
                'model' => $model,
            ]);
        }
    }

    /**
     * Deletes an existing StaticPage model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function actionDelete($id)
    {
		$model = $this->findModel($id);
        $model->isdel = 1;
        $model->save();
        //$model->delete(); //this will true delete

        return $this->redirect(['index']);
    }

    /**
     * Finds the StaticPage model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return StaticPage the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = StaticPage::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }
}


<?php

/** smtech\CanvasPest\CanvasArray */

namespace smtech\CanvasPest;

/**
 * An object to represent a list of Canvas Objects returned as a response from
 * the Canvas API.
 *
 * @author Seth Battis <SethBattis@stmarksschool.org>
 **/
class CanvasArray implements \Iterator, \ArrayAccess, \Serializable
{
    /** The maximum supported number of responses per page */
    const MAXIMUM_PER_PAGE = 100;

    /** @var CanvasPest $api Canvas API (for paging through the array) */
    protected $api;

    /**
     * @var string $endpoint API endpoint whose response is represented by this
     *      object
     **/
    private $endpoint = null;

    /**
     * @var CanvasPageLink[] $pagination The canonical (first, last, next,
     *      prev, current) pages relative to the current page of responses
     **/
    private $pagination = [];

    /**
     * @var array Cached pagination per each page response
     */
    private $paginationPerPage = [];

    /** @var CanvasObject[] $data Backing store */
    private $data = [];

    /** @var int $page Page number corresponding to current $key */
    private $page = null;

    /** @var int $key Current key-value of iterator */
    private $key = null;

    /**
     * Construct a CanvasArray
     *
     * @param string $jsonResponse A JSON-encoded response array from the
     *                             Canvas API
     * @param CanvasPest $canvasPest An API object for making pagination calls
     **/
    public function __construct($jsonResponse, $canvasPest)
    {
        $this->api = $canvasPest;

        $this->pagination = $this->parsePageLinks();

        /* locate ourselves */
        if (isset($this->pagination[CanvasPageLink::CURRENT])) {
            $this->page = $this->pagination[CanvasPageLink::CURRENT]->getPageNumber();
            $this->key = $this->pageNumberToKey($this->page);
            $this->paginationPerPage[$this->page] = $this->pagination;
        }

        /* parse the JSON response string */
        $key = $this->key;
        foreach (json_decode($jsonResponse, true) as $item) {
            $this->data[$key++] = new CanvasObject($item);
        }
    }

    /**
     * Parse the API response link headers into pagination information
     *
     * @param  boolean|string[] $headers (Optional, defaults to `$this->api->lastHeader('link')`)
     * @return CanvasPageLink[]
     */
    protected function parsePageLinks($headers = false)
    {
        $pagination = [];
        if (!$headers) {
            $headers = $this->api->lastHeader('link');
        }

        /* parse Canvas page links */
        if (preg_match_all('%<([^>]*)>\s*;\s*rel="([^"]+)"%', $headers, $links, PREG_SET_ORDER)) {
            foreach ($links as $link) {
                $pagination[$link[2]] = new CanvasPageLink($link[1], $link[2]);
            }
        }

        return $pagination;
    }
    /**
     * Convert a page number to an array key
     *
     * @param int $pageNumber 1-indexed page number
     *
     * @return int|false
     **/
    protected function pageNumberToKey($pageNumber)
    {
        if (isset($this->pagination[CanvasPageLink::CURRENT])) {
            return ($pageNumber - 1) * $this->pagination[CanvasPageLink::CURRENT]->getPerPage();
        }
        return false;
    }

    /**
     * Request a page of responses from the API
     *
     * A page of responses will be requested if it appears that that page has
     * not yet been loaded (tested by checking if the initial element of the
     * page has been initialized in the $data array).
     *
     * @param int $pageNumber Page number to request
     * @param bool $forceRefresh (Optional) Force a refresh of backing data,
     *                           even if cached (defaults to `FALSE`)
     *
     * @return bool `TRUE` if the page is requested, `FALSE` if it is already
     *                     cached (and therefore not requested)
     **/
    protected function requestPageNumber($pageNumber, $forceRefresh = false)
    {
        if (!isset($this->data[$this->pageNumberToKey($pageNumber)]) || ($forceRefresh && isset($this->api))) {
            // assume one page if no pagination (and already loaded)
            if (isset($this->pagination[CanvasPageLink::CURRENT])) {
                $params = $this->pagination[CanvasPageLink::CURRENT]->getParams();
                $params[CanvasPageLink::PARAM_PAGE_NUMBER] = $pageNumber;
                $page = $this->api->get($this->pagination[CanvasPageLink::CURRENT]->getEndpoint(), $params);
                $this->data = array_replace($this->data, $page->data);
                $pagination = $this->parsePageLinks();
                $this->paginationPerPage[$pagination[CanvasPageLink::CURRENT]->getPageNumber()] = $pagination;
                return true;
            }
        }
        return false;
    }

    /**
     * Request all pages from API
     *
     * This stores the entire API response locally, in preparation for, most
     * likely, serializing this object.
     *
     * @param bool $forceRefresh (Optional) Force a refresh of backing data,
     *                           even if cached (defaults to `FALSE`)
     *
     * @return void
     */
    protected function requestAllPages($forceRefresh = false)
    {
        $_page = $this->page;
        $_key = $this->key;

        $nextPageNumber = false;
        if (isset($this->pagination[CanvasPageLink::NEXT])) {
            $nextPageNumber = $this->pagination[CanvasPageLink::NEXT]->getPageNumber();
        }

        /* welp, here goes... let's hope we have a next page! */
        while ($nextPageNumber !== false) {
            $this->requestPageNumber($nextPageNumber, $forceRefresh);
            if (isset($this->paginationPerPage[$nextPageNumber][CanvasPageLink::NEXT])) {
                $nextPageNumber = $this->paginationPerPage[$nextPageNumber][CanvasPageLink::NEXT]->getPageNumber();
            } else {
                $nextPageNumber = false;
            }
        }

        $this->page = $_page;
        $this->key = $_key;
    }

    /***************************************************************************
     * ArrayObject methods
     */

    /**
     * Get the number of CanvasObjects in the Canvas response
     *
     * @return int
     *
     * @see http://php.net/manual/en/arrayobject.count.php ArrayObject::count
     **/
    public function count()
    {
        $this->requestAllPages();
        return count($this->data);
    }

    /**
     * Creates a copy of the CanvasArray
     *
     * @return CanvasObject[]
     *
     * @see http://php.net/manual/en/arrayobject.getarraycopy.php
     *      ArrayObject::getArrayCopy
     **/
    public function getArrayCopy()
    {
        $this->requestAllPages();
        return $this->data;
    }

    /***************************************************************************
     * ArrayAccess methods
     */

    /**
     * Whether an offset exists
     *
     * @param int|string $offset
     *
     * @return bool
     *
     * @see http://php.net/manual/en/arrayaccess.offsetexists.php
     *      ArrayAccess::offsetExists
     **/
    public function offsetExists($offset)
    {
        if (!isset($this->data[$offset])) {
            $this->requestAllPages();
        }
        return isset($this->data[$offset]);
    }

    /**
     * Offset to retrieve
     *
     * @param int|string $offset
     *
     * @return CanvasObject|null
     *
     * @see http://php.net/manual/en/arrayaccess.offsetexists.php
     *      ArrayAccess::offsetGet
     **/
    public function offsetGet($offset)
    {
        if (!isset($this->data[$offset])) {
            $this->requestAllPages();
        }
        return $this->data[$offset];
    }

    /**
     * Assign a value to the specified offset
     *
     * @deprecated CanvasObject and CanvasArray responses are immutable
     *
     * @param int|string $offset
     * @param CanvasObject $value
     *
     * @return void
     *
     * @throws CanvasArray_Exception IMMUTABLE All calls to this method will cause an exception
     *
     * @see http://php.net/manual/en/arrayaccess.offsetset.php
     *      ArrayAccess::offsetSet
     **/
    public function offsetSet($offset, $value)
    {
        throw new CanvasArray_Exception(
            'Canvas responses are immutable',
            CanvasArray_Exception::IMMUTABLE
        );
    }

    /**
     * Unset an offset
     *
     * @deprecated CanvasObject and CanvasArray responses are immutable
     *
     * @param int|string $offset
     *
     * @return void
     *
     * @throws CanvasArray_Exception IMMUTABLE All calls to this method will
     *         cause an exception
     *
     * @see http://php.net/manual/en/arrayaccess.offsetunset.php
     *      ArrayAccess::offsetUnset
     **/
    public function offsetUnset($offset)
    {
        throw new CanvasArray_Exception(
            'Canvas responses are immutable',
            CanvasArray_Exception::IMMUTABLE
        );
    }

    /**************************************************************************/

    /**************************************************************************
     * Iterator methods
     */

    /**
     * Return the current element
     *
     * @return CanvasObject
     *
     * @see http://php.net/manual/en/iterator.current.php Iterator::current
     **/
    public function current()
    {
        return $this->data[$this->key];
    }

    /**
     * Return the key of the current element
     *
     * @return int
     *
     * @see http://php.net/manual/en/iterator.key.php Iterator::key
     **/
    public function key()
    {
        return $this->key;
    }

    /**
     * Move forward to next element
     *
     * @return void
     *
     * @see http://php.net/manual/en/iterator.next.php Iterator::next
     **/
    public function next()
    {
        $this->key++;
    }

    /**
     * Rewind the iterator to the first element
     *
     * @return void
     *
     * @see http://php.net/manual/en/iterator.rewind.php Iterator::rewind
     **/
    public function rewind()
    {
        $this->key = 0;
    }

    /**
     * Checks if current position is valid
     *
     * @return bool
     *
     * @see http://php.net/manual/en/iterator.valid.php Iterator::valid
     **/
    public function valid()
    {
        return ($this->offsetExists($this->key));
    }

    /**************************************************************************/

    /***************************************************************************
     * Serializable methods
     */

    /**
     * String representation of CanvasArray
     *
     * @return string
     *
     * @see http://php.net/manual/en/serializable.serialize.php
     *      Serializable::serialize()
     **/
    public function serialize()
    {
        $this->requestAllPages();
        return serialize(
            array(
                'page' => $this->page,
                'key' => $this->key,
                'data' => $this->data
            )
        );
    }

    /**
     * Construct a CanvasArray from its string representation
     *
     * The data in the unserialized CanvasArray is static and cannot be
     * refreshed, as the CanvasPest API connection is _not_ serialized to
     * preserve the security of API access tokens.
     *
     * @param string $data
     *
     * @return string
     *
     * @see http://php.net/manual/en/serializable.unserialize.php
     *      Serializable::unserialize()
     **/
    public function unserialize($data)
    {
        $_data = unserialize($data);
        $this->page = $_data['page'];
        $this->key = $_data['key'];
        $this->data = $_data['data'];
        $this->api = null;
        $this->endpoint = null;
        $this->pagination = array();
    }
}


<?php

/** smtech\CanvasPest\CanvasPest */

namespace smtech\CanvasPest;

/**
 * An object to handle interactions with the Canvas API.
 *
 * For more information on the Canvas API refer to the offical
 * {@link https://canvas.instructure.com/doc/api/ Canvas API documentation} or
 * to the (slightly more up-to-date and pleasingly interactive)
 * {@link https://canvas.instructure.com/doc/api/live live documentation}.
 *
 * You can access the live documentation for your own Canvas instance and make
 * actual API calls to it at `https://<path-to-your-instance>/doc/api/live`
 *
 * @author Seth Battis <SethBattis@stmarksschool.org>
 **/
class CanvasPest extends \Battis\Educoder\Pest
{

    /** Name of the parameter controlling the number of responses per page */
    const PARAM_PER_PAGE = 'per_page';

    /** @var string[] $headers Additional headers to be passed to the API with each call */
    protected $headers;

    /**
     * Construct a new CanvasPest
     *
     * @api
     *
     * @param string $apiInstanceUrl URL of the API instance (e.g.
     *        `'https://canvas.instructure.com/api/v1'`)
     * @param string $apiAuthorizationToken (Optional) API access token for the
     *        API instance (if not provided now, it will need to be provided later)
     *
     * @see CanvasPest::setupToken() To configure the API access token later
     **/
    public function __construct($apiInstanceUrl, $apiAuthorizationToken = null)
    {
        parent::__construct($apiInstanceUrl);
        if (!empty($apiAuthorizationToken)) {
            $this->setupToken($apiAuthorizationToken);
        }
    }

    /**
     * Set up a new API access token to access this instance
     *
     * @param string $token API access token
     *
     * @return boolean
     *
     * @throws CanvasPest_Exception INVALID_TOKEN on an empty or non-string token value
     **/
    public function setupToken($token)
    {
        if (!empty($token)) {
            $this->headers['Authorization'] = "Bearer $token";
            return true;
        } else {
            if ($this->throw_exceptions) {
                throw new CanvasPest_Exception(
                    'API authorization token must be a non-zero-length string',
                    CanvasPest_Exception::INVALID_TOKEN
                );
            }
            return false;
        }
    }

    /**
     * Preprocess API call parameters before use
     *
     * Force maximum response page size, if not already defined.
     *
     * @param string[] $data Array of parameters for the next API call
     *
     * @return string[] Updated array of parameters
     *
     * @see CanvasArray::MAXIMUM_PER_PAGE Maximum number of responses per page
     * @see CanvasPest::PARAM_PER_PAGE Page size parameter
     **/
    private function preprocessData($data)
    {
        if (is_array($data) && !array_key_exists(self::PARAM_PER_PAGE, $data)) {
            $data[self::PARAM_PER_PAGE] = CanvasArray::MAXIMUM_PER_PAGE;
        }
        return $data;
    }

    /**
     * Prepare API request headers
     *
     * Flatten headers from an associative array to a numerically indexed array
     * of `"Name: Value"` style entries like `CURLOPT_HTTPHEADER` expects.
     * Numerically indexed arrays are not modified.
     *
     * Extended by CanvasPest to include the API access token in the
     * `Authorization` header.
     *
     * @param string[] $headers
     * @return string[]
     **/
    protected function prepHeaders($headers)
    {
        return parent::prepHeaders(array_merge($this->headers, $headers));
    }

    /**
     * Parse the API response into an object (or collection of objects).
     *
     * For queries to individually identified endpoints (e.g.
     * `accounts/1/users/123`), return a CanvasObject representing the API response
     * describing _that_ individually identified object affected by the query.
     *
     * For queries to generic endpoints (e.g. `accounts/1/users`), return a
     * traversable CanvasArray (of CanvasObjects) representing the API response
     * describing the list of objects affected by the query.
     *
     * @param string $response JSON-encoded response from the API
     *
     * @return CanvasObject|CanvasArray|false
     *
     * @throws CanvasPest_Exception INVALID_JSON_RESPONSE if an
     *         non-JSON-formatted response is received (and
     *         $this->throw_exceptions is true -- otherwise it returns false)
     **/
    protected function postprocessResponse($response)
    {
        if (substr($response, 0, 1) == '{') {
            return new CanvasObject($response);
        } elseif (substr($response, 0, 1) == '[') {
            return new CanvasArray($response, $this);
        } else {
            if ($this->throw_exceptions) {
                throw new CanvasPest_Exception(
                    $response,
                    CanvasPest_Exception::INVALID_JSON_RESPONSE
                );
            }
            return false;
        }
    }

    /**
     * Reformat query parameters for Canvas
     *
     * Specifically, Canvas expects no numeric indices for base array parameters.
     *
     * @param mixed $data
     *
     * @return string
     * @codingStandardsIgnoreStart
     **/
    protected function http_build_query($data)
    {
        // @codingStandardsIgnoreEnd
        return preg_replace('/%5B\d+%5D/simU', '[]', http_build_query($data));
    }

    /**
     * Make a GET call to the API
     *
     * For queries to individually identified endpoints (e.g.
     * `accounts/1/users/123`), return a CanvasObject representing the API response
     * describing _that_ individually identified object affected by the query.
     *
     * For queries to generic endpoints (e.g. `accounts/1/users`), return a
     * traversable CanvasArray (of CanvasObjects) representing the API response
     * describing the list of objects affected by the query.
     *
     * @api
     *
     * @param string $path Path to the API endpoint of this call
     * @param string|string[] $data (Optional) Query parameters for this call
     * @param string|string[] $headers (Optional) Any additional HTTP headers for this call
     *
     * @return CanvasObject|CanvasArray|false
     **/
    public function get($path, $data = array(), $headers = array())
    {
        return $this->postprocessResponse(
            parent::get($path, $this->preprocessData($data), $headers)
        );
    }

    /**
     * Make a POST call to the API
     *
     * For queries to individually identified endpoints (e.g.
     * `accounts/1/users/123`), return a CanvasObject representing the API response
     * describing _that_ individually identified object affected by the query.
     *
     * @api
     *
     * @param string $path Path to the API endpoint of this call
     * @param string|string[] $data (Optional) Query parameters for this call
     * @param string|string[] $headers (Optional) Any additional HTTP headers for this call
     *
     * @return CanvasObject|false
     **/
    public function post($path, $data = array(), $headers = array())
    {
        return $this->postprocessResponse(
            parent::post($path, $this->preprocessData($data), $headers)
        );
    }

    /**
     * Make a PUT call to the API
     *
     * For queries to individually identified endpoints (e.g.
     * `accounts/1/users/123`), return a CanvasObject representing the API response
     * describing _that_ individually identified object affected by the query.
     *
     * @api
     *
     * @param string $path Path to the API endpoint of this call
     * @param string|string[] $data (Optional) Query parameters for this call
     * @param string|string[] $headers (Optional) Any additional HTTP headers for this call
     *
     * @return CanvasObject|false
     **/
    public function put($path, $data = array(), $headers = array())
    {
        return $this->postprocessResponse(
            parent::put($path, $this->preprocessData($data), $headers)
        );
    }

    /**
     * Make a DELETE call to the API
     *
     * For queries to individually identified endpoints (e.g.
     * `accounts/1/users/123`), return a CanvasObject representing the API
     * response describing _that_ individually identified object affected by
     * the query.
     *
     * @api
     *
     * @param string $path Path to the API endpoint of this call
     * @param string|string[] $data (Optional) Query parameters for this call
     * @param string|string[] $headers (Optional) Any additional HTTP headers for this call
     *
     * @return CanvasObject|false
     **/
    public function delete($path, $data = array(), $headers = array())
    {
        if (!empty($data)) {
            $pathData = [];
            $pos = strpos($path, '?');
            if ($pos !== false) {
                parse_str(substr($path, $pos + 1), $pathData);
                $path = substr($path, 0, $pos);
            }
            $path .= '?' . $this->http_build_query(array_merge($pathData, $data));
        }
        return $this->postprocessResponse(
            parent::delete($path, $headers)
        );
    }

    /**
     * Make a PATCH call to the API
     *
     * @deprecated The Canvas API does not currently support PATCH calls
     *
     * @param string $path Path to the API endpoint of this call
     * @param string|string[] $data (Optional) Query parameters for this call
     * @param string|string[] $headers (Optional) Any additional HTTP headers for this call
     *
     * @return false
     *
     * @throws CanvasPest_Exception UNSUPPORTED_METHOD All calls to this method will cause an exception
     **/
    public function patch($path, $data = array(), $headers = array())
    {
        if ($this->throw_exceptions) {
            throw new CanvasPest_Exception(
                'The Canvas API does not support the PATCH method',
                CanvasPest_Exception::UNSUPPORTED_METHOD
            );
        }
        return false;
    }
}


<?php

namespace amilna\blog\controllers;

use Yii;
use amilna\blog\models\Post;
use amilna\blog\models\PostSearch;
use amilna\blog\models\BlogCatPos;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;

/**
 * PostController implements the CRUD actions for Post model.
 */
class PostController extends Controller
{
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['post'],
                ],
            ],
        ];
    }

    /**
     * Lists all Post models.
     * @params string $format, array $arraymap, string $term
     * @return mixed
     */
    public function actionIndex($format= false,$arraymap= false,$term = false,$category = false,$time = false)
    {
        $searchModel = new PostSearch();
        $req = Yii::$app->request->queryParams;
        if ($term) { $req[basename(str_replace("\\","/",get_class($searchModel)))]["term"] = $term;}
        if ($category) { $req[basename(str_replace("\\","/",get_class($searchModel)))]["category"] = $category;}
        if ($time) { $req[basename(str_replace("\\","/",get_class($searchModel)))]["time"] = $time;}
        $dataProvider = $searchModel->search($req);

        $query = $dataProvider->query;
        $query->andWhere(['status'=>[1]]);

        if ($format == 'json')
        {
			$model = [];
			foreach ($dataProvider->getModels() as $d)
			{
				$obj = $d->attributes;
				if ($arraymap)
				{
					$map = explode(",",$arraymap);
					if (count($map) == 1)
					{
						$obj = (isset($d[$arraymap])?$d[$arraymap]:null);
					}
					else
					{
						$obj = [];
						foreach ($map as $a)
						{
							$k = explode(":",$a);
							$v = (count($k) > 1?$k[1]:$k[0]);
							$obj[$k[0]] = ($v == "Obj"?json_encode($d->attributes):(isset($d->$v)?$d->$v:null));
						}
					}
				}

				if ($term)
				{
					if (!in_array($obj,$model))
					{
						array_push($model,$obj);
					}
				}
				else
				{
					array_push($model,$obj);
				}
			}
			header("Access-Control-Allow-Origin: *");
			header("Access-Control-Expose-Headers: X-Pagination-Per-Page,X-Pagination-Current-Page,X-Pagination-Page-Count,X-Pagination-Total-Count,Content-Type,Location");
			return \yii\helpers\Json::encode($model);
		}
		else
		{
			return $this->render('index', [
				'searchModel' => $searchModel,
				'dataProvider' => $dataProvider,
			]);
		}
    }

    public function actionAdmin($format= false,$arraymap= false,$term = false)
    {
        $searchModel = new PostSearch();
        $req = Yii::$app->request->queryParams;
        if ($term) { $req[basename(str_replace("\\","/",get_class($searchModel)))]["term"] = $term;}
        $dataProvider = $searchModel->search($req);

        if ($format == 'json')
        {
			$model = [];
			foreach ($dataProvider->getModels() as $d)
			{
				$obj = $d->attributes;
				if ($arraymap)
				{
					$map = explode(",",$arraymap);
					if (count($map) == 1)
					{
						$obj = (isset($d[$arraymap])?$d[$arraymap]:null);
					}
					else
					{
						$obj = [];
						foreach ($map as $a)
						{
							$k = explode(":",$a);
							$v = (count($k) > 1?$k[1]:$k[0]);
							$obj[$k[0]] = ($v == "Obj"?json_encode($d->attributes):(isset($d->$v)?$d->$v:null));
						}
					}
				}

				if ($term)
				{
					if (!in_array($obj,$model))
					{
						array_push($model,$obj);
					}
				}
				else
				{
					array_push($model,$obj);
				}
			}
			return \yii\helpers\Json::encode($model);
		}
		else
		{
			return $this->render('admin', [
				'searchModel' => $searchModel,
				'dataProvider' => $dataProvider,
			]);
		}
    }

    /**
     * Displays a single Post model.
     * @param integer $id
     * @additionalParam string $format
     * @return mixed
     */
    public function actionView($id,$format= false)
    {
        $model = $this->findModel($id);

        if ($format == 'json')
        {
			return \yii\helpers\Json::encode($model);
		}
		else
		{
			return $this->render('view', [
				'model' => $model,
			]);
		}
    }

    /**
     * Creates a new Post model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new Post();
        $model->time = date("Y-m-d H:i:s");
        $model->author_id = Yii::$app->user->id;
        $model->isdel = 0;

        if (Yii::$app->request->post())
        {
			$post = Yii::$app->request->post();
			$category = [];
			if (isset($post['Post']['category']))
			{
				$category = $post['Post']['category'];
			}

			if (is_array($post['Post']['tags']))
			{
				$post['Post']['tags'] = implode(",",$post['Post']['tags']);
			}

			$model->load($post);

			$transaction = Yii::$app->db->beginTransaction();
			try {

				if ($model->save()) {

					$cs = BlogCatPos::deleteAll("post_id = :id",["id"=>$model->id]);

					foreach ($category as $d)
					{
						$c = new BlogCatPos();
						$c->post_id = $model->id;
						$c->category_id = $d;
						$c->isdel = 0;
						$c->save();
					}

					$transaction->commit();
					return $this->redirect(['view', 'id' => $model->id]);
				}
				else
				{
					$model->id = array_merge($category,[]);
					$transaction->rollBack();
				}
			} catch (Exception $e) {
				$transaction->rollBack();
			}
		}

        return $this->render('create', [
			'model' => $model,
		]);
    }

    /**
     * Updates an existing Post model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);
		$model->tags = !empty($model->tags)?explode(",",$model->tags):[];

		if (Yii::$app->request->post())
        {
			$post = Yii::$app->request->post();
			$category = [];
			if (isset($post['Post']['category']))
			{
				$category = $post['Post']['category'];
			}

			if (is_array($post['Post']['tags']))
			{
				$post['Post']['tags'] = implode(",",$post['Post']['tags']);
			}

			$model->load($post);

			$transaction = Yii::$app->db->beginTransaction();
			try {

				if ($model->save()) {

					$cs = BlogCatPos::deleteAll("post_id = :id",["id"=>$model->id]);

					foreach ($category as $d)
					{
						//$c = BlogCatPos::find()->where("post_id = :id AND category_id = :aid",["id"=>$model->id,"aid"=>intval($d)])->one();
						//if (!$c)
						//{
							$c = new BlogCatPos();
						//}
						$c->post_id = $model->id;
						$c->category_id = $d;
						$c->isdel = 0;
						$c->save();
					}

					$transaction->commit();
					return $this->redirect(['view', 'id' => $model->id]);
				}
				else
				{
					$transaction->rollBack();
				}
			} catch (Exception $e) {
				$transaction->rollBack();
			}
		}

        return $this->render('update', [
			'model' => $model,
		]);
    }

    /**
     * Deletes an existing Post model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function actionDelete($id)
    {
		$model = $this->findModel($id);
        $model->isdel = 1;
        $model->save();
        //$model->delete(); //this will true delete

        return $this->redirect(['admin']);
    }

    /**
     * Finds the Post model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Post the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Post::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }
}


<?php

namespace amilna\blog\models;

use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use amilna\blog\models\Post;
use amilna\blog\models\BlogCatPos;

/**
 * PostSearch represents the model behind the search form about `amilna\blog\models\Post`.
 */
class PostSearch extends Post
{

	/*public $blogcatposId;*/
	public $authorName;
	public $term;
	public $category;

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['id', 'author_id', 'status', 'isdel'], 'integer'],
            [['title', 'description', 'content', 'tags', 'image', 'time', 'term', 'category','authorName'/*, blogcatposId*/], 'safe'],
            [['isfeatured'], 'boolean'],
        ];
    }

    public function attributeLabels()
    {
        return array_merge(parent::attributeLabels(),[
            'term' => Yii::t('app', 'Search'),
            'category' => Yii::t('app', 'Category'),
            'authorName' => Yii::t('app', 'Author'),
        ]);
    }

	public static function find()
	{
		return parent::find()->where([Post::tableName().'.isdel' => 0]);
	}

    /**
     * @inheritdoc
     */
    public function scenarios()
    {
        // bypass scenarios() implementation in the parent class
        return Model::scenarios();
    }

	private function queryString($fields)
	{
		$params = [];
		foreach ($fields as $afield)
		{
			$field = $afield[0];
			$tab = isset($afield[1])?$afield[1]:false;
			if (!empty($this->$field))
			{
				if (substr($this->$field,0,2) == "< " || substr($this->$field,0,2) == "> " || substr($this->$field,0,2) == "<=" || substr($this->$field,0,2) == ">=" || substr($this->$field,0,2) == "<>")
				{
					array_push($params,[str_replace(" ","",substr($this->$field,0,2)), "lower(".($tab?$tab.".":"").$field.")", strtolower(trim(substr($this->$field,2)))]);
				}
				else
				{
					array_push($params,["like", "lower(".($tab?$tab.".":"").$field.")", strtolower($this->$field)]);
				}
			}
		}
		return $params;
	}

	private function queryNumber($fields)
	{
		$params = [];
		foreach ($fields as $afield)
		{
			$field = $afield[0];
			$tab = isset($afield[1])?$afield[1]:false;
			if (!empty($this->$field))
			{
				$number = explode(" ",trim($this->$field));
				if (count($number) == 2)
				{
					if (in_array($number[0],['>','>=','<','<=','<>']) && is_numeric($number[1]))
					{
						array_push($params,[$number[0], ($tab?$tab.".":"").$field, $number[1]]);
					}
				}
				elseif (count($number) == 3)
				{
					if (is_numeric($number[0]) && is_numeric($number[2]))
					{
						array_push($params,['>=', ($tab?$tab.".":"").$field, $number[0]]);
						array_push($params,['<=', ($tab?$tab.".":"").$field, $number[2]]);
					}
				}
				elseif (count($number) == 1)
				{
					if (is_numeric($number[0]))
					{
						array_push($params,['=', ($tab?$tab.".":"").$field, str_replace(["<",">","="],"",$number[0])]);
					}
				}
			}
		}
		return $params;
	}

	private function queryTime($fields)
	{
		$params = [];
		foreach ($fields as $afield)
		{
			$field = $afield[0];
			$tab = isset($afield[1])?$afield[1]:false;
			if (!empty($this->$field))
			{
				$time = explode(" - ",$this->$field);
				if (count($time) > 1)
				{
					array_push($params,[">=", "concat('',".($tab?$tab.".":"").$field.")", $time[0]]);
					array_push($params,["<=", "concat('',".($tab?$tab.".":"").$field.")", $time[1]." 24:00:00"]);
				}
				else
				{
					if (substr($time[0],0,2) == "< " || substr($time[0],0,2) == "> " || substr($time[0],0,2) == "<=" || substr($time[0],0,2) == ">=" || substr($time[0],0,2) == "<>")
					{
						array_push($params,[str_replace(" ","",substr($time[0],0,2)), "concat('',".($tab?$tab.".":"").$field.")", trim(substr($time[0],2))]);
					}
					else
					{
						array_push($params,["like", "concat('',".($tab?$tab.".":"").$field.")", $time[0]]);
					}
				}
			}
		}
		return $params;
	}

    /**
     * Creates data provider instance with search query applied
     *
     * @param array $params
     *
     * @return ActiveDataProvider
     */
    public function search($params)
    {
        $query = $this->find();

        $query->joinWith(['author'/*,blogcatpos*/]);

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        $userClass = Yii::$app->getModule('blog')->userClass;

        /* uncomment to sort by relations table on respective column
		$dataProvider->sort->attributes['blogcatposId'] = [
			'asc' => ['{{%blogcatpos}}.id' => SORT_ASC],
			'desc' => ['{{%blogcatpos}}.id' => SORT_DESC],
		];*/
		$dataProvider->sort->attributes['authorName'] = [
			'asc' => [$userClass::tableName().'.username' => SORT_ASC],
			'desc' => [$userClass::tableName().'.username' => SORT_DESC],
		];

		$dataProvider->sort->attributes['term'] = [
			'asc' => ['title' => SORT_ASC],
			'desc' => ['title' => SORT_DESC],
		];

        if (!($this->load($params) && $this->validate())) {
            return $dataProvider;
        }

		$query->andFilterWhere([
            'status' => $this->status,
            'isdel' => $this->isdel,
            'isfeatured' => $this->isfeatured,
            /**/
        ]);

        $params = self::queryNumber([['id',$this->tableName()],['author_id']/*['id','{{%blogcatpos}}'],['id','{{%author}}']*/]);
		foreach ($params as $p)
		{
			$query->andFilterWhere($p);
		}
        $params = self::queryString([['title'],['description'],['content'],['tags'],['image']/*['id','{{%blogcatpos}}'],['id','{{%author}}']*/]);
		foreach ($params as $p)
		{
			$query->andFilterWhere($p);
		}
        $params = self::queryTime([['time']/*['id','{{%blogcatpos}}'],['id','{{%author}}']*/]);
		foreach ($params as $p)
		{
			$query->andFilterWhere($p);
		}

		$query->andFilterWhere(['like','lower('.$userClass::tableName().'.username)',strtolower($this->authorName)]);

		if ($this->category || $this->term)
		{

			$term = ($this->term?$this->term:$this->category);

			$dsn = $this->db->dsn;
			$cquery =  $this->find();
			if (strtolower(substr($dsn,0,5)) == "mysql")
			{
				$cquery->select(["GROUP_CONCAT(".$this->tableName().".id)"]);
			}
			else
			{
				$cquery->select(["array_agg(".$this->tableName().".id)"]);
			}

			$cquery->leftJoin(BlogCatPos::tableName()." as cp",$this->tableName().".id = cp.post_id")
					->leftJoin(Category::tableName()." as c","cp.category_id = c.id");

			if ($this->category)
			{
				$cquery->andWhere("lower(c.title) = '".strtolower($term)."'");
			}
			else
			{
				$cquery->andWhere("lower(c.title) like '%".strtolower($term)."%' or lower(c.description) like '%".strtolower($term)."%'");
			}

			$res = $cquery->scalar();
			$res = ($res == ""?"{}":$res);

			if ($this->category)
			{
				//$query->andFilterWhere(["OR","false",$this->tableName().".id = ANY ('".$res."')"]);
				$query->andFilterWhere(["OR","false","',".str_replace(["{","}"],"",$res).",' like concat('%,',".$this->tableName().".id,',%') "]);
			}
			else
			{
				$query->andFilterWhere(["OR","lower(title) like '%".strtolower($this->term)."%'",
					["OR","lower(description) like '%".strtolower($this->term)."%'",
						["OR","lower(tags) like '%".strtolower($this->term)."%'",
							["OR","lower(content) like '%".strtolower($this->term)."%'",
								//$this->tableName().".id = ANY ('".$res."')"
								"',".str_replace(["{","}"],"",$res).",' like concat('%,',".$this->tableName().".id,',%') "
							]
						]
					]
				]);
			}
		}

        return $dataProvider;
    }
}


<?php

namespace amilna\blog\controllers;

use Yii;
use amilna\blog\models\Banner;
use amilna\blog\models\BannerSearch;
use yii\data\ActiveDataProvider;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;

/**
 * BannerController implements the CRUD actions for Banner model.
 */
class BannerController extends Controller
{
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['post'],
                ],
            ],
        ];
    }

    /**
     * Lists all Banner models.
     * @params string $format, array $arraymap, string $term
     * @return mixed
     */
    public function actionIndex($format= false,$arraymap= false,$term = false)
    {

        $searchModel = new BannerSearch();
        $req = Yii::$app->request->queryParams;
        if ($term) { $req[basename(str_replace("\\","/",get_class($searchModel)))]["term"] = $term;}
        $dataProvider = $searchModel->search($req);

        if ($format == 'json')
        {
			$model = [];
			foreach ($dataProvider->getModels() as $d)
			{
				$obj = $d->attributes;
				if ($arraymap)
				{
					$map = explode(",",$arraymap);
					if (count($map) == 1)
					{
						$obj = (isset($d[$arraymap])?$d[$arraymap]:null);
					}
					else
					{
						$obj = [];
						foreach ($map as $a)
						{
							$k = explode(":",$a);
							$v = (count($k) > 1?$k[1]:$k[0]);
							$obj[$k[0]] = ($v == "Obj"?json_encode($d->attributes):(isset($d->$v)?$d->$v:null));
						}
					}
				}

				if ($term)
				{
					if (!in_array($obj,$model))
					{
						array_push($model,$obj);
					}
				}
				else
				{
					array_push($model,$obj);
				}
			}
			return \yii\helpers\Json::encode($model);
		}
		else
		{
			$bannerProvider = new ActiveDataProvider([
				'models' => Banner::find()->where('status = true AND isdel = 0 order by position asc')->all(),
				'pagination'=>false,
				/*'pagination' => [
					'pageSize' => 20,
				],*/
			]);

			return $this->render('index', [
				'searchModel' => $searchModel,
				'dataProvider' => $dataProvider,
				'bannerProvider' => $bannerProvider,
			]);
		}
    }

    /**
     * Displays a single Banner model.
     * @param integer $id
     * @additionalParam string $format
     * @return mixed
     */
    public function actionView($id,$format= false)
    {
        $model = $this->findModel($id);

        if ($format == 'json')
        {
			return \yii\helpers\Json::encode($model);
		}
		else
		{
			return $this->render('view', [
				'model' => $model,
			]);
		}
    }

    /**
     * Creates a new Banner model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new Banner();
        $model->time = date("Y-m-d H:i:s");
        $model->position = $model->getLast();
        $model->isdel = 0;

		$post = Yii::$app->request->post();
		if (isset($post['Banner']['tags']))
		{
			if (is_array($post['Banner']['tags']))
			{
				$post['Banner']['tags'] = implode(",",$post['Banner']['tags']);
			}
		}

		$transaction = Yii::$app->db->beginTransaction();
		try {
			if ($model->load($post) && $model->save()) {
				$model->updatePosition($model->position);
				$transaction->commit();
				return $this->redirect(['view', 'id' => $model->id]);
			}
			else
			{
				$transaction->rollBack();
			}
		} catch (Exception $e) {
			$transaction->rollBack();
		}

		return $this->render('create', [
			'model' => $model,
		]);

    }

    /**
     * Updates an existing Banner model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

		$model->tags = !empty($model->tags)?explode(",",$model->tags):[];

		$post = Yii::$app->request->post();
		if (isset($post['Banner']['tags']))
		{
			if (is_array($post['Banner']['tags']))
			{
				$post['Banner']['tags'] = implode(",",$post['Banner']['tags']);
			}
		}

		$transaction = Yii::$app->db->beginTransaction();
		try {
			if ($model->load($post) && $model->save()) {
				$model->updatePosition($model->position);
				$transaction->commit();
				return $this->redirect(['view', 'id' => $model->id]);
			}
			else
			{
				$transaction->rollBack();
			}
		} catch (Exception $e) {
			$transaction->rollBack();
		}

		return $this->render('update', [
			'model' => $model,
		]);

    }

    /**
     * Deletes an existing Banner model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function actionDelete($id)
    {
		$model = $this->findModel($id);
        $model->isdel = 1;
        $model->save();
        //$model->delete(); //this will true delete

        return $this->redirect(['index']);
    }

    /**
     * Finds the Banner model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Banner the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Banner::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }
}


<?php

namespace amilna\blog\controllers;

use Yii;
use amilna\blog\models\Gallery;
use amilna\blog\models\GallerySearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;

/**
 * GalleryController implements the CRUD actions for Gallery model.
 */
class GalleryController extends Controller
{
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['post'],
                ],
            ],
        ];
    }

    /**
     * Lists all Gallery models.
     * @params string $format, array $arraymap, string $term
     * @return mixed
     */
    public function actionIndex($format= false,$arraymap= false,$term = false,$tag = false)
    {
        $searchModel = new GallerySearch();
        $req = Yii::$app->request->queryParams;
        if ($term) {
			$req[basename(str_replace("\\","/",get_class($searchModel)))]["term"] = $term;
        }
        if ($tag) {
			$req[basename(str_replace("\\","/",get_class($searchModel)))]["tag"] = $tag;
        }
        $req[basename(str_replace("\\","/",get_class($searchModel)))]["status"] = 1;
        $dataProvider = $searchModel->search($req);

        if ($format == 'json')
        {
			$model = [];
			foreach ($dataProvider->getModels() as $d)
			{
				$obj = $d->attributes;
				if ($arraymap)
				{
					$map = explode(",",$arraymap);
					if (count($map) == 1)
					{
						$obj = (isset($d[$arraymap])?$d[$arraymap]:null);
					}
					else
					{
						$obj = [];
						foreach ($map as $a)
						{
							$k = explode(":",$a);
							$v = (count($k) > 1?$k[1]:$k[0]);
							$obj[$k[0]] = ($v == "Obj"?json_encode($d->attributes):(isset($d->$v)?$d->$v:null));
						}
					}
				}

				if ($term)
				{
					if (!in_array($obj,$model))
					{
						array_push($model,$obj);
					}
				}
				else
				{
					array_push($model,$obj);
				}
			}
			return \yii\helpers\Json::encode($model);
		}
		else
		{

			$tags=[];
			$items=[];
			$n = 0;
			$model = [];
			foreach ($dataProvider->getModels() as $data)
			{
				if (!empty($data->tags) && (!isset($req['GallerySearch']['tag'])))
				{
					foreach(explode(",",$data->tags) as $tag)
					{
						if (!isset($tags[strtolower($tag)]))
						{
							$n += 1;
							$tags[strtolower($tag)]=array('id'=>$n,'data'=>array(),'url'=>$data->url,'type'=>-1,'tags'=>$tag,'title'=>ucwords($tag),'description'=>$data->description);
							array_push($tags[strtolower($tag)]['data'],$data->image);
						}
						else
						{
							if (count($tags[strtolower($tag)]['data']) < 4)
							{
								array_push($tags[strtolower($tag)]['data'],$data->image);
							}
						}
					}
				}
				else
				{
					if (!isset($items[$data->id]))
					{
						$n += 1;
						$items[$data->id]=array('id'=>$n,'data'=>array(0=>$data->image),'url'=>$data->url,'type'=>$data->type,'tags'=>$data->tags,'title'=>$data->title,'description'=>$data->description);
					}
				}

			}
			$albums = array_merge($tags,$items);

			return $this->render('index', [
				'searchModel' => $searchModel,
				'dataProvider' => $dataProvider,
				'albums' => $albums,
			]);
		}
    }

    public function actionAdmin($format= false,$arraymap= false,$term = false)
    {
        $searchModel = new GallerySearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams+($term?['GallerySearch'=>['search'=>$term]]:[]));

        if ($format == 'json')
        {
			$model = [];
			foreach ($dataProvider->getModels() as $d)
			{
				$obj = $d->attributes;
				if ($arraymap)
				{
					$map = explode(",",$arraymap);
					if (count($map) == 1)
					{
						$obj = (isset($d[$arraymap])?$d[$arraymap]:null);
					}
					else
					{
						$obj = [];
						foreach ($map as $a)
						{
							$k = explode(":",$a);
							$v = (count($k) > 1?$k[1]:$k[0]);
							$obj[$k[0]] = ($v == "Obj"?json_encode($d->attributes):(isset($d->$v)?$d->$v:null));
						}
					}
				}

				if ($term)
				{
					if (!in_array($obj,$model))
					{
						array_push($model,$obj);
					}
				}
				else
				{
					array_push($model,$obj);
				}
			}
			return \yii\helpers\Json::encode($model);
		}
		else
		{
			return $this->render('admin', [
				'searchModel' => $searchModel,
				'dataProvider' => $dataProvider,
			]);
		}
    }

    /**
     * Displays a single Gallery model.
     * @param integer $id
     * @additionalParam string $format
     * @return mixed
     */
    public function actionView($id,$format= false)
    {
        $model = $this->findModel($id);

        if ($format == 'json')
        {
			return \yii\helpers\Json::encode($model);
		}
		else
		{
			return $this->render('view', [
				'model' => $model,
			]);
		}
    }

    /**
     * Creates a new Gallery model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new Gallery();
        $model->time = date("Y-m-d H:i:s");
        $model->type = 0;
        $model->isdel = 0;

		$post = Yii::$app->request->post();
		if (isset($post['Gallery']['tags']))
		{
			if (is_array($post['Gallery']['tags']))
			{
				$post['Gallery']['tags'] = implode(",",$post['Gallery']['tags']);
			}
		}

        if ($model->load($post) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }

    /**
     * Updates an existing Gallery model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

		$model->tags = !empty($model->tags)?explode(",",$model->tags):[];

		$post = Yii::$app->request->post();
		if (isset($post['Gallery']['tags']))
		{
			if (is_array($post['Gallery']['tags']))
			{
				$post['Gallery']['tags'] = implode(",",$post['Gallery']['tags']);
			}
		}

        if ($model->load($post) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('update', [
                'model' => $model,
            ]);
        }
    }

    /**
     * Deletes an existing Gallery model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function actionDelete($id)
    {
		$model = $this->findModel($id);
        $model->isdel = 1;
        $model->save();
        //$model->delete(); //this will true delete

        return $this->redirect(['index']);
    }

    /**
     * Finds the Gallery model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Gallery the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Gallery::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }
}


<?php

namespace Mouf\Database\SchemaAnalyzer;

use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\VoidCache;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Schema\Table;
use Fhaculty\Graph\Edge\Base;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Vertex;

/**
 * This class can analyze a database model.
 * In this class you will find.
 *
 * - Functions to automatically detect **junction tables**
 * - Functions to compute the shortest path between 2 tables based on the relationships stored in the schema.
 */
class SchemaAnalyzer
{
    private static $WEIGHT_FK = 1;
    private static $WEIGHT_INHERITANCE_FK = 0.1;
    private static $WEIGHT_JOINTURE_TABLE = 1.5;

    const WEIGHT_IMPORTANT = 0.75;
    const WEIGHT_IRRELEVANT = 2;
    const WEIGHT_IGNORE = INF;

    /**
     * @var AbstractSchemaManager
     */
    private $schemaManager;

    /**
     * @var Schema
     */
    private $schema;

    /**
     * @var Cache
     */
    private $cache;

    /**
     * @var string
     */
    private $cachePrefix;

    /**
     * Nested arrays containing table => column => cost.
     *
     * @var float[][]
     */
    private $alteredCosts = [];

    /**
     * Array containing table cost.
     *
     * @var float[]
     */
    private $alteredTableCosts = [];

    /**
     * @param AbstractSchemaManager $schemaManager
     * @param Cache|null            $cache          The Doctrine cache service to use to cache results (optional)
     * @param string|null           $schemaCacheKey The unique identifier for the schema manager. Compulsory if cache is set.
     */
    public function __construct(AbstractSchemaManager $schemaManager, Cache $cache = null, $schemaCacheKey = null)
    {
        $this->schemaManager = $schemaManager;
        if (empty($schemaCacheKey) && $cache) {
            throw new SchemaAnalyzerException('You must provide a schema cache key if you configure SchemaAnalyzer with cache support.');
        }
        if ($cache) {
            $this->cache = $cache;
        } else {
            $this->cache = new VoidCache();
        }
        $this->cachePrefix = $schemaCacheKey;
    }

    /**
     * Detect all junctions tables in the schema.
     * A table is a junction table if:.
     *
     * - it has exactly 2 foreign keys
     * - it has only 2 columns (or 3 columns if the third one is an autoincremented primary key).
     *
     * If $ignoreReferencedTables is true, junctions table that are pointed to by a foreign key of another
     * table are ignored.
     *
     * @param bool $ignoreReferencedTables
     *
     * @return Table[]
     */
    public function detectJunctionTables($ignoreReferencedTables = false)
    {
        $junctionTablesKey = $this->cachePrefix.'_junctiontables_'.($ignoreReferencedTables ? 'true' : 'false');
        $junctionTables = $this->cache->fetch($junctionTablesKey);
        if ($junctionTables === false) {
            $junctionTables = array_filter($this->getSchema()->getTables(), function (Table $table) use ($ignoreReferencedTables) {
                return $this->isJunctionTable($table, $ignoreReferencedTables);
            });
            $this->cache->save($junctionTablesKey, $junctionTables);
        }

        return $junctionTables;
    }

    /**
     * Returns true if $table is a junction table.
     * I.e:.
     *
     * - it must have exactly 2 foreign keys
     * - it must have only 2 columns (or 3 columns if the third one is an autoincremented primary key).
     *
     * If $ignoreReferencedTables is true, junctions table that are pointed to by a foreign key of another
     * table are ignored.
     *
     * @param Table $table
     * @param bool  $ignoreReferencedTables
     *
     * @return bool
     */
    public function isJunctionTable(Table $table, $ignoreReferencedTables = false)
    {
        $foreignKeys = $table->getForeignKeys();
        if (count($foreignKeys) !== 2) {
            return false;
        }

        $columns = $table->getColumns();
        if (count($columns) < 2 || count($columns) > 3) {
            return false;
        }

        if ($table->hasPrimaryKey()) {
            $pkColumns = $table->getPrimaryKeyColumns();
        } else {
            $pkColumns = [];
        }

        if (count($pkColumns) === 1 && count($columns) === 2) {
            return false;
        }

        if (count($pkColumns) !== 1 && count($columns) === 3) {
            return false;
        }

        $fkColumnNames = [];
        foreach ($foreignKeys as $foreignKey) {
            $fkColumns = $foreignKey->getColumns();
            if (count($fkColumns) !== 1) {
                return false;
            }
            $fkColumnNames[$fkColumns[0]] = true;
        }

        if (count($columns) === 3) {
            // Let's check that the third column (the ID is NOT a foreign key)
            if (isset($fkColumnNames[$pkColumns[0]])) {
                return false;
            }

            // Let's check that the primary key is autoincremented
            $pkColumn = $table->getColumn($pkColumns[0]);
            if (!$pkColumn->getAutoincrement() && strpos($pkColumn->getComment(), '@Autoincrement') === false) {
                return false;
            }
        }

        if ($ignoreReferencedTables && $this->isTableReferenced($table)) {
            return false;
        }

        return true;
    }

    /**
     * Returns true if the table $table is referenced by another table.
     *
     * @param Table $table
     *
     * @return bool
     */
    private function isTableReferenced(Table $table)
    {
        $tableName = $table->getName();
        foreach ($this->getSchema()->getTables() as $tableIter) {
            foreach ($tableIter->getForeignKeys() as $fk) {
                if ($fk->getForeignTableName() === $tableName) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Get the shortest path between 2 tables.
     *
     * @param string $fromTable
     * @param string $toTable
     *
     * @return \Doctrine\DBAL\Schema\ForeignKeyConstraint[]
     *
     * @throws SchemaAnalyzerException
     */
    public function getShortestPath($fromTable, $toTable)
    {
        return $this->fromCache($this->cachePrefix.'_shortest_'.$fromTable.'```'.$toTable, function () use ($fromTable, $toTable) {
            return $this->getShortestPathWithoutCache($fromTable, $toTable);
        });
    }

    /**
     * Get the shortest path between 2 tables.
     *
     * @param string $fromTable
     * @param string $toTable
     *
     * @return \Doctrine\DBAL\Schema\ForeignKeyConstraint[]
     *
     * @throws SchemaAnalyzerException
     */
    private function getShortestPathWithoutCache($fromTable, $toTable)
    {
        $this->checkTableExists($fromTable);
        $this->checkTableExists($toTable);

        $graph = $this->buildSchemaGraph();

        try {
            $predecessors = MultiDijkstra::findShortestPaths($graph->getVertex($fromTable), $graph->getVertex($toTable));
            $edges = MultiDijkstra::getCheapestPathFromPredecesArray($graph->getVertex($fromTable), $graph->getVertex($toTable), $predecessors);
        } catch (MultiDijkstraAmbiguityException $e) {
            // If there is more than 1 short path, let's display this.
            $paths = MultiDijkstra::getAllPossiblePathsFromPredecesArray($graph->getVertex($fromTable), $graph->getVertex($toTable), $predecessors);
            $msg = $this->getAmbiguityExceptionMessage($paths, $graph->getVertex($fromTable), $graph->getVertex($toTable));
            throw new ShortestPathAmbiguityException($msg);
        }

        $foreignKeys = [];

        $currentTable = $fromTable;

        foreach ($edges as $edge) {
            /* @var $edge Base */

            if ($fk = $edge->getAttribute('fk')) {
                /* @var $fk ForeignKeyConstraint */
                $foreignKeys[] = $fk;
                if ($fk->getForeignTableName() == $currentTable) {
                    $currentTable = $fk->getLocalTable()->getName();
                } else {
                    $currentTable = $fk->getForeignTableName();
                }
            } elseif ($junctionTable = $edge->getAttribute('junction')) {
                /* @var $junctionTable Table */
                $junctionFks = array_values($junctionTable->getForeignKeys());
                // We need to order the 2 FKs. The first one is the one that has a common point with the current table.
                $fk = $junctionFks[0];
                if ($fk->getForeignTableName() == $currentTable) {
                    $foreignKeys[] = $fk;
                    $foreignKeys[] = $junctionFks[1];
                    $currentTable = $junctionFks[1]->getForeignTableName();
                } else {
                    $foreignKeys[] = $junctionFks[1];
                    $foreignKeys[] = $fk;
                    $currentTable = $fk->getForeignTableName();
                }
            } else {
                // @codeCoverageIgnoreStart
                throw new SchemaAnalyzerException('Unexpected edge. We should have a fk or a junction attribute.');
                // @codeCoverageIgnoreEnd
            }
        }

        return $foreignKeys;
    }

    private function checkTableExists($tableName)
    {
        try {
            $this->getSchema()->getTable($tableName);
        } catch (SchemaException $e) {
            throw SchemaAnalyzerTableNotFoundException::tableNotFound($tableName, $this->schema, $e);
        }
    }

    private function buildSchemaGraph()
    {
        $graph = new Graph();

        // First, let's create all the vertex
        foreach ($this->getSchema()->getTables() as $table) {
            $graph->createVertex($table->getName());
        }

        // Then, let's create all the edges
        foreach ($this->getSchema()->getTables() as $table) {
            $fks = $this->removeDuplicates($table->getForeignKeys());
            foreach ($fks as $fk) {
                // Create an undirected edge, with weight = 1
                $edge = $graph->getVertex($table->getName())->createEdge($graph->getVertex($fk->getForeignTableName()));
                if (isset($this->alteredCosts[$fk->getLocalTable()->getName()][implode(',', $fk->getLocalColumns())])) {
                    $cost = $this->alteredCosts[$fk->getLocalTable()->getName()][implode(',', $fk->getLocalColumns())];
                } elseif ($this->isInheritanceRelationship($fk)) {
                    $cost = self::$WEIGHT_INHERITANCE_FK;
                } else {
                    $cost = self::$WEIGHT_FK;
                }
                if (isset($this->alteredTableCosts[$fk->getLocalTable()->getName()])) {
                    $cost *= $this->alteredTableCosts[$fk->getLocalTable()->getName()];
                }

                $edge->setWeight($cost);
                $edge->getAttributeBag()->setAttribute('fk', $fk);
            }
        }

        // Finally, let's add virtual edges for the junction tables
        foreach ($this->detectJunctionTables() as $junctionTable) {
            $tables = [];
            foreach ($junctionTable->getForeignKeys() as $fk) {
                $tables[] = $fk->getForeignTableName();
            }

            $edge = $graph->getVertex($tables[0])->createEdge($graph->getVertex($tables[1]));
            $cost = self::$WEIGHT_JOINTURE_TABLE;
            if (isset($this->alteredTableCosts[$junctionTable->getName()])) {
                $cost *= $this->alteredTableCosts[$junctionTable->getName()];
            }
            $edge->setWeight($cost);
            $edge->getAttributeBag()->setAttribute('junction', $junctionTable);
        }

        return $graph;
    }

    /**
     * Remove duplicate foreign keys (assumes that all foreign yes are from the same local table).
     *
     * @param ForeignKeyConstraint[] $foreignKeys
     * @return ForeignKeyConstraint[]
     */
    private function removeDuplicates(array $foreignKeys)
    {
        $fks = [];
        foreach ($foreignKeys as $foreignKey) {
            $fks[implode('__`__', $foreignKey->getLocalColumns())] = $foreignKey;
        }

        return array_values($fks);
    }

    /**
     * Returns the schema (from the schema manager or the cache if needed).
     *
     * @return Schema
     */
    private function getSchema()
    {
        if ($this->schema === null) {
            $schemaKey = $this->cachePrefix.'_schema';
            $this->schema = $this->cache->fetch($schemaKey);
            if (empty($this->schema)) {
                $this->schema = $this->schemaManager->createSchema();
                $this->cache->save($schemaKey, $this->schema);
            }
        }

        return $this->schema;
    }

    /**
     * Returns the full exception message when an ambiguity arises.
     *
     * @param Base[][] $paths
     * @param Vertex   $startVertex
     */
    private function getAmbiguityExceptionMessage(array $paths, Vertex $startVertex, Vertex $endVertex)
    {
        $textPaths = [];
        $i = 1;
        foreach ($paths as $path) {
            $textPaths[] = 'Path '.$i.': '.$this->getTextualPath($path, $startVertex);
            ++$i;
        }

        $msg = sprintf("There are many possible shortest paths between table '%s' and table '%s'\n\n",
            $startVertex->getId(), $endVertex->getId());

        $msg .= implode("\n\n", $textPaths);

        return $msg;
    }

    /**
     * Returns the textual representation of the path.
     *
     * @param Base[] $path
     * @param Vertex $startVertex
     */
    private function getTextualPath(array $path, Vertex $startVertex)
    {
        $currentVertex = $startVertex;
        $currentTable = $currentVertex->getId();

        $textPath = $currentTable;

        foreach ($path as $edge) {
            /* @var $fk ForeignKeyConstraint */
            if ($fk = $edge->getAttribute('fk')) {
                if ($fk->getForeignTableName() == $currentTable) {
                    $currentTable = $fk->getLocalTable()->getName();
                    $isForward = false;
                } else {
                    $currentTable = $fk->getForeignTableName();
                    $isForward = true;
                }

                $columns = implode(',', $fk->getLocalColumns());

                $textPath .= ' '.(!$isForward ? '<' : '');
                $textPath .= '--('.$columns.')--';
                $textPath .= ($isForward ? '>' : '').' ';
                $textPath .= $currentTable;
            } elseif ($junctionTable = $edge->getAttribute('junction')) {
                /* @var $junctionTable Table */
                $junctionFks = array_values($junctionTable->getForeignKeys());
                // We need to order the 2 FKs. The first one is the one that has a common point with the current table.
                $fk = $junctionFks[0];
                if ($fk->getForeignTableName() == $currentTable) {
                    $currentTable = $junctionFks[1]->getForeignTableName();
                } else {
                    $currentTable = $fk->getForeignTableName();
                }
                $textPath .= ' <=('.$junctionTable->getName().')=> '.$currentTable;
            } else {
                // @codeCoverageIgnoreStart
                throw new SchemaAnalyzerException('Unexpected edge. We should have a fk or a junction attribute.');
                // @codeCoverageIgnoreEnd
            }
        }

        return $textPath;
    }

    /**
     * Sets the cost of a foreign key.
     *
     * @param string $tableName
     * @param string $columnName
     * @param float  $cost
     *
     * @return $this
     */
    public function setForeignKeyCost($tableName, $columnName, $cost)
    {
        $this->alteredCosts[$tableName][$columnName] = $cost;
    }

    /**
     * Sets the cost modifier of a table.
     *
     * @param string $tableName
     * @param float  $cost
     *
     * @return $this
     */
    public function setTableCostModifier($tableName, $cost)
    {
        $this->alteredTableCosts[$tableName] = $cost;
    }

    /**
     * Sets the cost modifier of all tables at once.
     *
     * @param array<string, float> $tableCosts The key is the table name, the value is the cost modifier.
     */
    public function setTableCostModifiers(array $tableCosts)
    {
        $this->alteredTableCosts = $tableCosts;
    }

    /**
     * Sets the cost of all foreign keys at once.
     *
     * @param array<string, array<string, float>> $fkCosts First key is the table name, second key is the column name, the value is the cost.
     */
    public function setForeignKeyCosts(array $fkCosts)
    {
        $this->alteredCosts = $fkCosts;
    }

    /**
     * Returns true if this foreign key represents an inheritance relationship,
     * i.e. if this foreign key is based on a primary key.
     *
     * @param ForeignKeyConstraint $fk
     *
     * @return true
     */
    private function isInheritanceRelationship(ForeignKeyConstraint $fk)
    {
        if (!$fk->getLocalTable()->hasPrimaryKey()) {
            return false;
        }
        $fkColumnNames = $fk->getLocalColumns();
        $pkColumnNames = $fk->getLocalTable()->getPrimaryKeyColumns();

        sort($fkColumnNames);
        sort($pkColumnNames);

        return $fkColumnNames == $pkColumnNames;
    }

    /**
     * If this table is pointing to a parent table (if its primary key is a foreign key pointing on another table),
     * this function will return the pointed table.
     * This function will return null if there is no parent table.
     *
     * @param string $tableName
     *
     * @return ForeignKeyConstraint|null
     */
    public function getParentRelationship($tableName)
    {
        return $this->fromCache($this->cachePrefix.'_parent_'.$tableName, function () use ($tableName) {
            return $this->getParentRelationshipWithoutCache($tableName);
        });
    }

    /**
     * If this table is pointing to a parent table (if its primary key is a foreign key pointing on another table),
     * this function will return the pointed table.
     * This function will return null if there is no parent table.
     *
     * @param string $tableName
     *
     * @return ForeignKeyConstraint|null
     */
    private function getParentRelationshipWithoutCache($tableName)
    {
        $table = $this->getSchema()->getTable($tableName);
        foreach ($table->getForeignKeys() as $fk) {
            if ($this->isInheritanceRelationship($fk)) {
                return $fk;
            }
        }

        return;
    }

    /**
     * If this table is pointed by children tables (if other child tables have a primary key that is also a
     * foreign key to this table), this function will return the list of child tables.
     * This function will return an empty array if there are no children tables.
     *
     * @param string $tableName
     *
     * @return ForeignKeyConstraint[]
     */
    public function getChildrenRelationships($tableName)
    {
        return $this->fromCache($this->cachePrefix.'_children_'.$tableName, function () use ($tableName) {
            return $this->getChildrenRelationshipsWithoutCache($tableName);
        });
    }

    /**
     * If this table is pointed by children tables (if other child tables have a primary key that is also a
     * foreign key to this table), this function will return the list of child tables.
     * This function will return an empty array if there are no children tables.
     *
     * @param string $tableName
     *
     * @return ForeignKeyConstraint[]
     */
    private function getChildrenRelationshipsWithoutCache($tableName)
    {
        $schema = $this->getSchema();
        $children = [];
        foreach ($schema->getTables() as $table) {
            if ($table->getName() === $tableName) {
                continue;
            }
            $fks = $this->removeDuplicates($table->getForeignKeys());
            foreach ($fks as $fk) {
                if ($fk->getForeignTableName() === $tableName && $this->isInheritanceRelationship($fk)) {
                    $children[] = $fk;
                }
            }
        }

        return $children;
    }

    /**
     * Returns an item from cache or computes it using $closure and puts it in cache.
     *
     * @param string   $key
     * @param callable $closure
     *
     * @return mixed
     */
    private function fromCache($key, callable $closure)
    {
        $item = $this->cache->fetch($key);
        if ($item === false) {
            $item = $closure();
            $this->cache->save($key, $item);
        }

        return $item;
    }
}


<?php
namespace LanguageSwitcher\View\Helper;

use Cake\Core\Configure;
use Cake\Core\InstanceConfigTrait;
use Cake\Routing\Router;
use Cake\Utility\Hash;
use Cake\Utility\Text;
use Cake\View\Helper;
use Cake\View\View;

/**
 * LanguageSwitcher helper
 */
class LanguageSwitcherHelper extends Helper
{

    use InstanceConfigTrait;

    protected $_defaultConfig = [
        'availableLanguages' => [
            'en_US' => 'en_US'
        ],
        'displayNames' => [
            'en_US' => 'English'
        ],
        'imageMapping' => [
            'en_US' => 'United-States'
        ],
        'renderToggleButtonDisplayName' => true,
        'element' => 'LanguageSwitcher.language_switcher'
    ];

    /**
     * Renders language switcher dropdown
     *
     * @return element
     */
    public function renderLanguageSwitcher()
    {
        return $this->_View->element($this->config('element'), [
            'availableLanguages' => $this->config('availableLanguages'),
            'displayNames' => $this->config('displayNames'),
            'imageMapping' => $this->config('imageMapping'),
            'renderToggleButtonDisplayName' => $this->config('renderToggleButtonDisplayName')
        ]);
    }

    /**
     * Merge current GET parameters with the language string
     *
     * @param string $language Language
     * @return string Url
     */
    public function getUrl($language)
    {
        $lang = ['lang' => $language];
        $query = Hash::merge($this->request->query, $lang);
        $urlArray = Hash::merge($this->request->params['pass'], ['?' => $query]);

        return Router::url($urlArray);
    }
}


<?php
namespace LanguageSwitcher\Middleware;

use App\Lib\Environment;
use Cake\Core\Configure;
use Cake\Core\InstanceConfigTrait;
use Cake\I18n\I18n;
use Cake\I18n\Time;
use Cake\Network\Session;
use Cake\ORM\TableRegistry;
use Locale;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;
use Throwable;

class LanguageSwitcherMiddleware
{

    use InstanceConfigTrait;

    protected $_defaultConfig = [
        'model' => 'Users',
        'field' => 'language',
        'Cookie' => [
            'name' => 'ChoosenLanguage',
            'expires' => '+1 year',
            'domain' => ''
        ],
        'availableLanguages' => [
            'en_US' => 'en_US'
        ],
        'beforeSaveCallback' => null,
        'additionalConfigFiles' => []
    ];

    /**
     * Constructor.
     *
     * @param array $config config
     */
    public function __construct($config = [])
    {
        $this->config($config);
        if (empty($this->config('Cookie.domain'))) {
            throw new RuntimeException('Missing config Cookie.domain for ' . get_class($this));
        }
    }

    /**
     * Sets the locale to user locale or browser locale
     *
     * @param ServerRequestInterface $request  The request.
     * @param ResponseInterface $response The response.
     * @param callable $next The next middleware to call.
     *
     * @return \Psr\Http\Message\ResponseInterface A response.
     */
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
    {
        $cookieLocale = null;
        if (isset($request->getCookieParams()[$this->__getCookieName()])) {
            $cookieLocale = $request->getCookieParams()[$this->__getCookieName()];
        }

        $queryLocale = $this->__getQueryLocale($request);
        $session = $request->getAttribute('session');
        $user = $session->read('Auth.User');

        if (isset($user)) {
            $usersTable = TableRegistry::get($this->config('model'));

            try {
                $user = $usersTable->get($user['id']);
            } catch (Throwable $t) {
                // Return early if the user is not found.
                return $this->__next($request, $response, $next);
            }

            $beforeSaveCallback = $this->config('beforeSaveCallback');
            if (isset($beforeSaveCallback)
                && is_callable($beforeSaveCallback)
            ) {
                $beforeSaveCallback($user, $request, $response);
            }

            if (!isset($user->{$this->config('field')})) {
                $user->{$this->config('field')} = $cookieLocale;
                $usersTable->save($user);
            }

            if (isset($queryLocale) && in_array($queryLocale, $this->__getAllowedLanguages())) {
                if ($user->{$this->config('field')} !== $queryLocale) {
                    $user->{$this->config('field')} = $queryLocale;
                    $usersTable->save($user);
                }
            }

            $this->__setCookieAndLocale($user->{$this->config('field')});

            return $this->__next($request, $response, $next);
        }

        if (isset($queryLocale)) {
            $this->__setCookieAndLocale($queryLocale);

            return $this->__next($request, $response, $next);
        }

        if (!isset($queryLocale) && isset($cookieLocale)) {
            I18n::locale($cookieLocale);

            return $this->__next($request, $response, $next);
        }

        $locale = Locale::acceptFromHttp($request->getHeaderLine('Accept-Language'));
        if (!$locale) {
            return $this->__next($request, $response, $next);
        }
        if ($this->__getAllowedLanguages() !== ['*']) {
            $locale = Locale::lookup($this->__getAllowedLanguages(), $locale, true, Configure::read('App.defaultLocale'));
            if ($locale === '') {
                $locale = Configure::read('App.defaultLocale');
            }
        }
        if ($locale || $this->__getAllowedLanguages() === ['*']) {
            $this->__setCookieAndLocale($locale);
        }

        return $this->__next($request, $response, $next);
    }

    /**
     * Calls the next middleware.
     *
     * @param ServerRequestInterface $request  The request.
     * @param ResponseInterface $response The response.
     * @param callable $next The next middleware to call.
     *
     * @return \Psr\Http\Message\ResponseInterface A response.
     */
    private function __next(ServerRequestInterface $request, ResponseInterface $response, $next)
    {
        $this->__loadConfigFiles();

        return $next($request, $response);
    }

    /**
     * Get Query Locale
     * @param  ServerRequestInterface $request  The request.
     * @return string       locale string
     */
    private function __getQueryLocale($request)
    {
        if (isset($request->getQueryParams()['lang'])) {
            return $request->getQueryParams()['lang'];
        }
    }

    /**
     * Set the cookie and the locale
     *
     * @param string $locale locale
     * @return void
     */
    private function __setCookieAndLocale($locale)
    {
        // @FIXME Should be refactored when cake 3.4 was released
        if (PHP_SAPI !== 'cli') {
            $time = $this->__getCookieExpireTime();
            I18n::locale($locale);
            setcookie($this->__getCookieName(), $locale, $time, '/', $this->config('Cookie.domain'));
        }
    }

    /**
     * Loads additional config files that require the language to be set correctly.
     *
     * @return void
     */
    private function __loadConfigFiles()
    {
        $additionalConfigs = $this->config('additionalConfigFiles');
        foreach ($additionalConfigs as $additionalConfig) {
            Configure::load($additionalConfig);
        }
    }

    /**
     * Get all allowed browser languages
     *
     * @return array
     */
    private function __getAllowedLanguages()
    {
        return $this->config('availableLanguages');
    }

    /**
     * Get the cookie name
     *
     * @return string
     */
    private function __getCookieName()
    {
        return $this->config('Cookie.name');
    }

    /**
     * Get the cookie expiration date
     *
     * @return int
     */
    private function __getCookieExpireTime()
    {
        $time = new Time($this->config('Cookie.expires'));

        return $time->toUnixString();
    }
}