<?php

class DefaultBehavior extends ModelBehavior {

	function beforeFind(Model $Model, $query) {

		# Allow sorting
		if (key_exists('sort', $query)) {
			$query['pack'] = true;
			$Model->sort = $query['sort'];
		}

		# Handle invalid order columns
		if ($Model->isModule) {
			$order = isset($query['order']) ? $query['order'] : [];
			foreach ($order as $index => $orderParam) {
				if (!is_array($orderParam)) {
					continue;
				}
				foreach ($orderParam as $column => $direction) {
					if (strpos($column, $Model->alias) === false) {
						if (substr($Model->alias, 0, 3) == 'Cms') {
							$newColumn = str_replace(substr($Model->alias, 3), $Model->alias, $column);
							unset($order[$index][$column]);
							$order[$index][$newColumn] = $direction;
						}
					}
				}
			}
			$query['order'] = $order;
		}

		Timer::startLoop('DefaultBehavior::beforeFind()');
		{

			# Get locale
			if (!isset($query['locale'])) {
				$query['locale'] = Configure::read('Config.language');
			}

			# Apply restrictions #
			$query['conditions'] = array_merge((array) $query['conditions'], (array) $Model->getRestrictions());

			# Add virtual fields for polyglot fields
			foreach ($Model->tableToModel as $name) {
				$object = $name != $Model->name ? (isset($Model->$name) ? $Model->$name : null) : $Model;
				if ($object) {
					foreach ($object->polyglotFields as $field) {
						$object->virtualFields[$field] = "{$object->alias}.{$field}__" . $query['locale'];
					}

					break;
				}
			}

			# Search all polyglot fields
			if (!empty($query['full']) && !empty($query['fields'])) {
				foreach ($query['fields'] as $i => $field) {
					$params = $Model->schema($field);
					if (!empty($params['polyglot'])) {

						# Remove original name
						unset($query['fields'][$i]);

						# Add polyglot field names
						foreach (Configure::read('Config.Languages') as $locale => $language) {
							$query['fields'][] = $field . '__' . $locale;
						}
					}
				}
			}

			# Only for front
			if (!CMS && empty($query['all'])) {

				# Restrict to only current language
				if ($Model->schema("is_translated__{$query['locale']}") && $query['locale'] && empty($query['raw'])) {
					$query['conditions']["{$Model->alias}.is_translated__{$query['locale']}"] = true;

					# Allow only records with defined slug
					if ($Model->schema("slug__{$query['locale']}") && empty($query['raw'])) {
						$query['conditions']["{$Model->alias}.slug__{$query['locale']} <>"] = '';
					}
				}

				# Restict to only active
				if ($Model->schema('is_active') && !isset($query['show_inactive'])) {
					$query['conditions']["{$Model->alias}.is_active"] = true;
				}

				# Restict to only non-deleted
				if ($Model->schema('is_deleted')) {
					$query['conditions']["{$Model->alias}.is_deleted"] = false;
				}

				# Restict to only approved
				if (!isset($query['ignoreApproval'])) {
					$query['ignoreApproval'] = false;
				}
				if ($Model->schema('approval_status') && (!$Model->ignoreApproval && !$query['ignoreApproval'])) {
					$query['conditions']["{$Model->alias}.approval_status"] = 'Accepted';
				}
			}

			# Set the pack flag
			if (!empty($query['pack'])) {
				$Model->afterPack = $query['pack'];
			}

		}
		Timer::endLoop('DefaultBehavior::beforeFind()');

		return $this->query = $query;
	}

	# ~ Default afterFind transformations  - - - - - - - - - - - - - - - - - - - - #
	function afterFind(Model $Model, $results, $primary = false) {

		Timer::startLoop('DefaultBehavior::afterFind()');
		{

			# Polyglot
			$language = Configure::read('Config.Languages');
			if ($language) {
				foreach ($Model->polyglotFields as $field) {
					foreach ($results as $i => $result) {
						foreach ($language as $locale => $params) {
							$associations = array_merge(array_keys($Model->hasOne), array_keys($Model->belongsTo), [ $Model->alias ]);

							foreach ($associations as $alias) {
								if (@array_key_exists("{$field}__{$locale}", $result[$alias])) {
									$results[$i][$alias]["{$field}__"][$locale] = $result[$alias]["{$field}__{$locale}"];
									unset($results[$i][$alias]["{$field}__{$locale}"]);
								}
							}

							foreach ($Model->hasMany as $association => $associationData) {
								if ($associationData['className'] != $Model->name || !isset($result[$association])) {
									continue;
								}

								foreach ($result[$association] as $childIndex => $child) {
									if (@array_key_exists("{$field}__{$locale}", $child)) {
										$results[$i][$association][$childIndex]["{$field}__"][$locale] = $child["{$field}__{$locale}"];
										unset($results[$i][$association][$childIndex]["{$field}__{$locale}"]);
									}
								}
							}
						}
					}
				}
			}

			# Forced fields
			if (!empty($Model->forceFields)) {
				foreach ($Model->forceFields as $model => $data) {
					foreach ($data as $field => $value) {
						$results[0][$model][$field] = $value;
					}
				}
			}

			# Pack results by primary key
			if (!empty($Model->afterPack)) {
				$key = $Model->afterPack !== true ? $Model->afterPack : null;
				$results = $Model->packResults($results, $key);
				unset($Model->afterPack);
			}

			# Set proper data type
			foreach ($results as $i => $result) {
				if (isset($result[$Model->alias])) {
					foreach ($result[$Model->alias] as $key => $value) {
						if (isset($Model->_schema[$key]) && $value !== null) {
							switch ($Model->_schema[$key]['type']) {
								case 'integer':
								case 'biginteger':
									$results[$i][$Model->alias][$key] = (int) $value;
									break;

								case 'boolean':
									$results[$i][$Model->alias][$key] = (bool) $value;
									break;

								case 'float':
								case 'double':
									$results[$i][$Model->alias][$key] = (double) $value;
									break;
							}
						}

						if (substr($key, 0, 7) == 'cached_') {
							$alias = Inflector::classify(substr($key, 7));
							$results[$i][$alias]['file'] = $value;
							$results[$i][$alias]['path'] = pathinfo($value, PATHINFO_DIRNAME) . '/';
							$results[$i][$alias]['filename'] = pathinfo($value, PATHINFO_BASENAME);
						}
					}
				}
			}

			// Manual sort
			if (property_exists($Model, 'sort')) {
				$results = manualSort($results, $Model->sort);
				unset($Model->sort);
			}
		}
		Timer::endLoop('DefaultBehavior::afterFind()');

		return $results;
	}

	function beforeValidate(Model $Model, $options = []) {

		Timer::startLoop('DefaultBehavior::beforeValidate()');
		{
			$languages = Configure::read('Config.Languages');
			if (count($languages) > 1 && ($Model->isModule || isset($Model->data['Page'])) && $Model->displayField != 'id') {

				# Check if display field is translated.
				foreach ($languages as $locale => $params) {

					if (isset($Model->data[$Model->alias][$Model->displayField . "__" . $locale])) {
						$displayFieldValue = $Model->data[$Model->alias][$Model->displayField . "__" . $locale];
					} else {
						if (isset($Model->previousData[$Model->alias][$Model->displayField . "__" . $locale]))
							$displayFieldValue = $Model->previousData[$Model->alias][$Model->displayField . "__" . $locale];
					}

					# If not translated, set is_translated__[locale] to false.
					if (isset($displayFieldValue) && empty($displayFieldValue)) {
						$Model->data[$Model->alias]['is_translated__' . $locale] = 0;
					}
				}
			}

			if ($Model->isMultiple = isset($Model->data['Multiple'])) {
				foreach ($Model->data['Multiple'] as $fieldName => $use)
					if (!$use) {
						unset($Model->data[$Model->alias][$fieldName]);
					}
			}

			foreach ($Model->validate as $field => $rules) {
				if (!empty($Model->_schema[$field]['polyglot'])) {
					foreach (Configure::read('Config.Languages') as $locale => $params) {
						if (!isset($Model->data[$Model->alias]['is_translated__' . $locale]) || $Model->data[$Model->alias]['is_translated__' . $locale]) {
							$Model->validate["{$field}__{$locale}"] = $rules;
						}
					}
					unset($Model->validate[$field]);
				}
			}

			// Unset nonuploaded upload items
			$associations = [ 'hasOne', 'hasMany' ];
			foreach ($associations as $association) {
				foreach ($Model->$association as $alias => $data) {
					if (isset($Model->data[$alias]) && !empty($Model->data[$alias]['new']) && empty(@$Model->data[$alias]['new']['file']['tmp_name'])) {
						unset($Model->data[$alias]);
					}
				}
			}
		}
		Timer::startLoop('DefaultBehavior::beforeValidate()');

		return true;
	}

	# ~ Default beforeSave transformations - - - - - - - - - - - - - - - - - - - - #
	function beforeSave(Model $Model, $options = []) {

		Timer::startLoop('DefaultBehavior::beforeSave()');
		{

			# Prevent page_id error
			if ($Model->schema('page_id') && isset($Model->data[$Model->alias]['page_id'])) {
				$pageIdField = $Model->schema('page_id');
				if (!$pageIdField['null'] && empty($Model->data[$Model->alias]['page_id'])) {
					$Model->invalidate('page_id', __('Please select page'));
					return false;
				}
				if ($Model->data[$Model->alias]['page_id'] === '') {
					$Model->data[$Model->alias]['page_id'] = null;
				}
			}

			# Previous values
			$Model->previousData = !empty($Model->data[$Model->alias]['id'])
				? $Model->find('first', [ 'conditions' => [ "{$Model->alias}.{$Model->primaryKey}" => $Model->data[$Model->alias]['id'] ], 'callbacks' => false ])
				: null;

			# Default values
			$currentAdministrator = CakeSession::read('Auth.Administrator.id');
			if (!empty($currentAdministrator)) {
				$Model->data[$Model->alias]['modified_by'] = CakeSession::read('Auth.Administrator.id');
			} else {
				unset($Model->data[$Model->alias]['modified_by']);
			}
			if (!empty($Model->data[$Model->alias]['modified_by'])) {
				if (empty($Model->data[$Model->alias][$Model->primaryKey])) {
					$Model->data[$Model->alias]['created_by'] = $_SESSION['Auth']['Administrator']['id'];
					if (empty($Model->data[$Model->alias]['owned_by']) && $Model->schema('owned_by')) {
						$Model->data[$Model->alias]['owned_by'] = $_SESSION['Auth']['Administrator']['id'];
					}
					if (empty($Model->data[$Model->alias]['assigned_to']) && $Model->schema('assigned_to')) {
						$Model->data[$Model->alias]['assigned_to'] = $_SESSION['Auth']['Administrator']['id'];
					}
				}
			}

			# Multiple records save
			if ($Model->isMultiple) {
				$data = [];
				$numbers = explode('/', $Model->data[$Model->alias]['id']);
				unset($Model->data[$Model->alias]['id'], $Model->data[$Model->alias]['modified']);
				if (!empty($Model->data[$Model->alias])) {
					foreach ($numbers as $id) {
						$data[][$Model->alias] = array_merge(
							[ 'id' => $id ],
							$Model->data[$Model->alias]
						);
					}
					$Model->isMultiple = false;
					$Model->saveAll($data);
				}
				$Model->data = [ $Model->alias => [] ];
				$Model->itemsSaved = sizeof($data);
			}

			# Slug
			if ($Model->schema('slug')) {
				$Model->noSlug = true;

				foreach ($Model->data[$Model->alias] as $field => $value) {
					@list($fieldname, $locale) = explode('__', $field);
					if ($fieldname == $Model->slugSource) {

						# Get the slug multilingular name
						$slugName = isset($locale) ? 'slug__' . $locale : 'slug';

						# Make slug
						if ($slug = $Model->createSlug($value, $slugName, $Model->data[$Model->alias], $locale)) {
							$Model->noSlug = false;
						}
					}
				}
			}

			# Singles
			foreach ($Model->singles as $single) {
				/* Baguje dodavanje jezika bez dva poziva za hasAny */
				$Model->hasAny([ $Model->alias . '.' . $single => true, $Model->alias . '.id <>' => $Model->id ]);
				if (isset($Model->data[$Model->alias][$single]) && !$Model->data[$Model->alias][$single] && !$Model->hasAny([ $Model->alias . '.' . $single => true, $Model->alias . '.id <>' => $Model->id ])) {
					$Model->data[$Model->alias][$single] = true;
				}
			}

			# Some special fields
			foreach ($Model->data[$Model->alias] as $field => $value) {
				switch ($field) {

					# Add protocol to urls
					case 'url':
					case 'website':
						if (isset($value{0}) && $value{0} != '/' && !strpos($value, '://') && !preg_match('~^[a-z_]+:~', $value)) {
							$Model->data[$Model->alias][$field] = "http://{$value}";
						}
						break;
				}
			}

			# Content
			$this->preparedContent = false;
			if (isset($Model->data['Content']['fields'])) {

				# Ensure that fields are last in the array
				$fields = $Model->data['Content']['fields'];
				unset($Model->data['Content']['fields']);
				array_push($Model->data['Content'], $fields);

				# Prepare the content for later insert
				$Model->preparedContent = ClassRegistry::init('Content')->prepare($Model->data);
				if ($Model->preparedContent === false) {
					return false;
				}
			}

			# Manual ordering
			if (isset($Model->data[$Model->alias]['manualOrdering'])) {
				$Model->skipAutoOrdering = true;
				$Model->data[$Model->alias]['ordering'] = $Model->data[$Model->alias]['manualOrdering'];
				unset($Model->data[$Model->alias]['manualOrdering']);
			}

			# Parent id
			if (isset($Model->data[$Model->alias]['parent_id']) && empty($Model->data[$Model->alias]['parent_id'])) {
				$Model->data[$Model->alias]['parent_id'] = null;
			}

			if ($Model->polyglotFields) {
				foreach ($Model->polyglotFields as $polyglotField) {
					if (array_key_exists($polyglotField, $Model->data[$Model->alias])) {
						unset($Model->data[$Model->alias][$polyglotField]);
					}
				}
			}

		}
		Timer::startLoop('DefaultBehavior::beforeSave()');

		return true;
	}

	function afterSave(Model $Model, $created, $options = []) {

		Timer::startLoop('DefaultBehavior::afterSave()');
		{

			if (isset($Model->data[$Model->alias]['moduleconnections_generated_id']) && !empty($Model->data[$Model->alias]['moduleconnections_generated_id'])) {
				ClassRegistry::init('ModuleConnection')->reattachGenerated($Model->data[$Model->alias]['moduleconnections_generated_id'], $Model->id);
			}

			# Ordering
			if (isset($Model->data[$Model->alias]['ordering']) && empty($Model->skipAutoOrdering)) {
				$prev = isset($Model->previousData[$Model->alias]['ordering'])
					? $Model->previousData[$Model->alias]['ordering']
					: PHP_INT_MAX;
				$next = (int) $Model->data[$Model->alias]['ordering'];
				if ($prev != $next) {
					if ($prev > $next) {
						$alt = '>';
						$mark = '<';
						$sign = '+';
					} else {
						$alt = '<';
						$mark = '>';
						$sign = '-';
					}

					# Find grouping field, if any
					$group = [];
					foreach ($Model->orderingGroups as $fieldName)
						if ($Model->schema($fieldName)) {

							# Get the value of grouping field
							$value = isset($Model->data[$Model->alias][$fieldName])
								? $Model->data[$Model->alias][$fieldName]
								: $Model->field($fieldName);

							# Add condition
							$group = [ "{$Model->alias}.{$fieldName}" => $value ];
							break;
						}

					$Model->updateAll(
						[ "{$Model->alias}.ordering" => "{$Model->alias}.ordering {$sign} 1" ],
						$group + [
							"{$Model->alias}.ordering {$mark}"        => $prev,
							"{$Model->alias}.ordering {$alt}="        => $next,
							"{$Model->alias}.{$Model->primaryKey} <>" => $Model->id ]
					);
				}
			}

			# Singles
			foreach ($Model->singles as $single) {
				if (!empty($Model->data[$Model->alias][$single])) {
					$Model->updateAll(
						[ $Model->alias . '.' . $single => 'false' ],
						[ $Model->alias . '.id <>' => $Model->id ]
					);
				}
			}

			# Slug
			if (!empty($Model->noSlug)) {
				$slugs = [];

				# Get the proper slug source
				foreach ($Model->data[$Model->alias] as $field => $value) {
					@list($fieldname, $locale) = explode('__', $field);

					if ($fieldname == $Model->slugSource) {
						$slugs[isset($locale) ? 'slug__' . $locale : 'slug'] = $Model->id;
					}
				}

				$Model->noSlug = false;
				if (!empty($slugs)) {
					$Model->save([ 'id' => $Model->id ] + $slugs);
				}
			}

			# Content
			if (isset($Model->preparedContent) && !empty($Model->data)) {
				if (!ClassRegistry::init('Content')->insert($Model->preparedContent, $Model->data)) {
					return false;
				}
			}

			if (isset($Model->data[$Model->alias]['approval_status'])) {
				if (isset($Model->previousData[$Model->alias]['approval_status']) &&
					$Model->previousData[$Model->alias]['approval_status'] != $Model->data[$Model->alias]['approval_status']) {
					$Model->approvalStatusChanged($Model->previousData[$Model->alias]['approval_status'], $Model->data[$Model->alias]['approval_status']);
				}
			}

			return true;

		}
		Timer::startLoop('DefaultBehavior::beforeSave()');

	}

	# ~ Default beforeDelete behaviour - - - - - - - - - - - - - - - - - - - - - - #
	function beforeDelete(Model $Model, $cascade = true) {

		# Check restrictions
		if (!$Model->find('first', $Model->id)) {
			return false;
		}

		if ($Model->hardDelete) {
			return true;
		}

		# Save deleted data for callbacks
		$Model->deletedRecord = $Model->data;

		# Soft Delete
		$administrator = !empty($_SESSION['Auth']['Administrator']) ? $_SESSION['Auth']['Administrator'] : 0;
		$Model->softDeleted = false;

		# If record is soft deletable but not soft deleted.
		if ($Model->schema('is_deleted') && !$Model->field('is_deleted')) {
			$Model->softDeleted = true;
			$Model->saveField('is_deleted', true);

			if (method_exists($Model, 'afterSoftDelete')) {
				$Model->afterSoftDelete();
			}
			return false;

			# If record is soft deletable and is soft deleted.
		} else if ($Model->schema('is_deleted') && $Model->field('is_deleted')) {
			if ($administrator['group_id'] != 1) {
				return false;
			}
			return $this->checkIntegrity($Model);
		}

		return true;
	}

	/**
	 * Updates the database after a successfull delete.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function afterDelete(Model $Model) {

		# Ordering #
		if (isset($Model->deletedRecord[$Model->alias]['ordering']) && empty($Model->skipAutoOrdering)) {
			$prev = isset($Model->deletedRecord[$Model->alias]['ordering']);

			# Find grouping field, if any
			$group = [];
			foreach ($Model->orderingGroups as $fieldName)
				if ($Model->schema($fieldName)) {

					# Get the value of grouping field
					$value = isset($Model->data[$Model->alias][$fieldName])
						? $Model->data[$Model->alias][$fieldName]
						: $Model->field($fieldName);

					# Add condition
					$group = [ "{$Model->alias}.{$fieldName}" => $value ];
					break;
				}

			$Model->updateAll(
				[ "{$Model->alias}.ordering" => "{$Model->alias}.ordering - 1" ],
				$group + [
					"{$Model->alias}.ordering >"              => $prev,
					"{$Model->alias}.{$Model->primaryKey} <>" => $Model->deletedRecord[$Model->alias]['id'] ]
			);

		}

		# Comments
		if ($Model->hasComments) {
			ClassRegistry::init('Comment')->deleteAll([ 'Comment.model' => $Model->alias, 'Comment.foreign_key' => $Model->deletedRecord[$Model->alias]['id'] ]);
		}
	}

	/**
	 * Check db integrity and disallow deletion of referenced records.
	 *
	 * @param    Model
	 *        Model data.
	 *
	 * @return
	 *        Exception if check not passed, true if all OK.
	 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	public function checkIntegrity($Model) {

		# Generate foreign key name and get all available modules.
		$foreignKey = 'cms_' . Inflector::singularize(Inflector::tableize($Model->name)) . '_id';
		$modules = Stash::read('modules');

		foreach ($modules as $module) {

			# Skip this model.
			if ($module['Module']['name'] == $Model->name)
				continue;
			$instance = ClassRegistry::init($module['Module']['name']);

			# Check all field.
			foreach ($instance->schema() as $key => $field) {
				if ($key == $foreignKey) {

					# If key name matches, check its value.
					$matches = $instance->find('first', [
							'fields'     => [ $module['Module']['name'] . '.id',
								$module['Module']['name'] . '.' . $instance->displayField ],
							'conditions' =>
								[ $module['Module']['name'] . '.' . $key => $Model->id ]
						]
					);

					# If there is matches, abort.
					if (!empty($matches)) {
						throw new Exception(__('Delete failed.') . ' ' . __('This item is connected to another module item') . '. (' . __($module['Module']['name']) . ': ' . __($matches[$instance->name][$instance->displayField]) . ')');
						return false;
					}
				}
			}
		}

		return true;
	}

	/**
	 * Refresh upload cache fields.
	 *
	 * @param Model    $Model The model whose cache should be refreshed.
	 * @param int|null $id    Set to refresh for one record.
	 */
	public function refreshUploadCache(Model $Model, $id = null) {

		$hasChanges = false;

		// Store cached uploads paths
		foreach ($Model->hasOne as $alias => $options) {

			// Skip non uploads
			if ($options['className'] != 'Upload') {
				continue;
			}

			// Generate cached field
			$field = 'cached_' . Inflector::underscore($alias);

			$ids = [];

			// If field exists - update
			if ($Model->schema($field)) {

				// If for specific id
				if ($id) {
					$ids = [ $id ];
				}

				// Build conditions
				$conditions = [
					'model'       => $Model->alias,
					'association' => $alias,
					"filename <> ''",
					"filename IS NOT NULL"
				];

				if ($ids) {
					$conditions['foreign_key'] = $ids;
				}

				// Get existing uploads
				$uploads = ClassRegistry::init('Upload')->find('list', [
					'fields'     => [ 'foreign_key', 'file' ],
					'conditions' => $conditions
				]);

				// Get existing items
				$existingItems = $Model->find('list', [
					'fields'     => [ 'id', $field ],
					'conditions' => $ids ? [ $Model->alias . '.id' => $ids ] : []
				]);

				// Compare and update
				$itemsToSave = [];
				foreach ($uploads as $modelId => $cachedPath) {

					if (isset($existingItems[$modelId]) && $cachedPath == $existingItems[$modelId]) {
						continue;
					}

					$itemsToSave[] = [
						'id'   => $modelId,
						$field => $cachedPath
					];
				}

				if ($itemsToSave) {
					$Model->saveMany($itemsToSave, [
						'validate'  => false,
						'callbacks' => false
					]);
					$hasChanges = true;
				}

			}

		}

		if ($hasChanges) {
			$Model->invalidateCache();
		}

	}
}
