diff options
Diffstat (limited to 'site/app/app_model.php')
-rw-r--r-- | site/app/app_model.php | 246 |
1 files changed, 107 insertions, 139 deletions
diff --git a/site/app/app_model.php b/site/app/app_model.php index 0ff9958..7375d5f 100644 --- a/site/app/app_model.php +++ b/site/app/app_model.php @@ -57,7 +57,7 @@ class AppModel extends Model } return parent::__construct($id, $table, $ds); } - + /** * This function will dynamically join translations into the current find operation, * according to whichever fields it finds in $this->translated_fields. @@ -70,29 +70,23 @@ class AppModel extends Model // Tell the user they are bad because they don't have a model name. if (!isset($this->name)) { - trigger_error('No model name was found for class: '.$get_class($this).'.', E_NOTICE); + trigger_error('No model name was found for class: '.$get_class($this).'.', E_NOTICE); } - + if (!$this->translate) return true; // don't do anything if translation was deactivated - + // This will build a finderQuery for the translations, and bind our current model to the translations table on the fly if (isset($this->translated_fields) && is_array($this->translated_fields)) { - + // Allow querying for a locale other than currently set $lang = $this->getLang(); - // fallback language is usually English. Some models have special - // fallback options, however, so we are handling them here. - switch ($this->name) { - case 'Addon': + // fallback language is usually English. If we are selecting addons however, + // we fall back to what's defined for that addon. + if ($this->name == 'Addon') { $fb_locale = '`Addon`.`defaultlocale`'; - break; - case 'Collection': - $fb_locale = '`Collection`.`defaultlocale`'; - break; - default: + } else { $fb_locale = "'en-US'"; - break; } // These parts are separated due to the way the query is built @@ -102,15 +96,15 @@ class AppModel extends Model // Generate a field list just like Cake would do it, so that we // know which translations to join in. // If the user didn't give us a field list, we use the default field - // list set for this Model. We have to have cake + // list set for this Model. We have to have cake // generate the list for us now, because once we set a fields - // array, Cake won't select any other fields anymore than the ones + // array, Cake won't select any other fields anymore than the ones // we request. if (!empty($queryData['fields'])) $_fields = $queryData['fields']; else $_fields = $this->default_fields; - + // if it's a string only, wrap it into an array so all the // following array magic works with it as well if (is_string($_fields)) $_fields = array($_fields); @@ -122,15 +116,15 @@ class AppModel extends Model if (false === $pos = array_search("`{$this->name}`.`{$field}`", $_fields)) { continue; } - - // for each translated field, we select the localized string, + + // for each translated field, we select the localized string, // automatically falling back to en-US if nothing is found. - // We also fetch the locale, which will be the requested + // We also fetch the locale, which will be the requested // locale if found and en-US in case of fallback. // naming is {fieldname} and {fieldname}_locale resp., which // means, fallback is transparent. $_select = "IFNULL(`tr_{$field}`.localized_string, `fb_{$field}`.localized_string) AS `{$field}`"; - + // replace the translation id with the translation unless explicitly opted out of if ($this->translationReplace === false) { // append the translation @@ -143,12 +137,12 @@ class AppModel extends Model // (that is: the requested locale if the localized string // is not null, otherwise the fallback locale) $_fields[] = "IF(!ISNULL(`tr_{$field}`.localized_string), `tr_{$field}`.locale, `fb_{$field}`.locale) AS `{$field}_locale`"; - + // Our query design requires us to join on the same table repeatedly. // Each join requires a different table name, so we're actually // calling our tables the same things as the fields. We're also - // creating a string for the fallback versions (usually en-US). + // creating a string for the fallback versions (usually en-US). // The requested locale has the prefix "tr_" (as "translation") // and the fallback has the prefix "fb_". $_joins[] = "LEFT JOIN translations AS `tr_{$field}` ON (`{$this->name}`.`{$field}` = `tr_{$field}`.id AND `tr_{$field}`.locale='{$lang}')"; @@ -232,7 +226,7 @@ class AppModel extends Model /** * query(), checking for cached result objects (only on select queries, * of course). - * Note: If you execute multiple queries in one line with a select query + * Note: If you execute multiple queries in one line with a select query * first, followed by some writing (insert or so), this *will* break. * Don't do this. */ @@ -241,11 +235,11 @@ class AppModel extends Model && is_string($query) && (0 === strpos(strtolower(ltrim($query)), 'select')) && isset($this->name)) { - + $cachekey = $this->_cachekey('query:'.$query); if ($cached = $this->Cache->get($cachekey)) return $cached; } - + if ($use_shadow_database && !defined('SHADOW_DISABLED')) { $this->useDbConfig = 'shadow'; $result = parent::query($query, $cakeCaching); @@ -253,9 +247,9 @@ class AppModel extends Model } else { $result = parent::query($query, $cakeCaching); } - + if ($this->caching && !empty($cachekey) && $result !== false) { - // cache it (if it's a select query, otherwise $cachekey + // cache it (if it's a select query, otherwise $cachekey // would be empty) $res = $this->Cache->set($cachekey, $result); } @@ -280,7 +274,7 @@ class AppModel extends Model return MEMCACHE_PREFIX.md5($key); } - + /** * Allowed querying for a locale other than currently set. @@ -315,86 +309,76 @@ class AppModel extends Model } /** - * extended field validation: allow arbitrary validation functions - * to use, add 'fieldname' => VALID_NOT_EMPTY or similar to $this->$validate, - * then add a method clean_fieldname($input) which in the case of invalidity - * calls $this->invalidate('fieldname') or amends $this->validationErrors. - * - * @param array $data data to be validated, $this->data by default - * @return array validationError, array() if none + * Remora overwrites the default application model in order to implement + * more sophisticated validation methods as described in: + * http://wiki.cakephp.org/tutorials:advanced_validation + * http://bakery.cakephp.org/articles/view/55 */ - function invalidFields($data=array()) { - if (!$this->beforeValidate()) { + function invalidFields($data = array()) { + + if(!$this->beforeValidate()) { return false; } - parent::invalidFields($data); - if (empty($data)) { + + if (!isset($this->validate) || empty($this->validate)) { + if (!empty($this->validationErrors)) { + return $this->validationErrors; + } else { + return array(); + } + } + + + if (!isset($this->data)) { + + $this->set($data); + } elseif (!empty($data)) { + $data = array_merge($data, $this->data); + $this->set($data); + } else { $data = $this->data; } - foreach (array_keys($this->validate) as $field) { - $func = 'clean_'.$field; - if (method_exists($this, $func) && isset($data[$this->name][$field])) { - call_user_func(array($this, $func), $data[$this->name][$field]); + + $errors = array(); + + foreach ($data as $table => $field) { + foreach ($this->validate as $field_name => $validators) { + if (!is_array($validators)) $validators = array(array($validators)); // wrap validator into array if it's only one + foreach($validators as $validator) { + if (!is_array($validator)) $validator = array($validator); // wrap validator into array if it's only one + if (isset($validator[0])) { + if (method_exists($this, $validator[0])) { + if (isset($data[$table][$field_name]) && !call_user_func(array(&$this, $validator[0]))) { + if (!isset($errors[$field_name])) { + $errors[$field_name] = isset($validator[1]) ? $validator[1] : 1; + } + } + } else { + if (isset($data[$table][$field_name]) && is_string($data[$table][$field_name]) && !preg_match($validator[0], $data[$table][$field_name])) { + if (!isset($errors[$field_name])) { + $errors[$field_name] = isset($validator[1]) ? $validator[1] : 1; + } + } + } + } + } } } + $this->validationErrors = array_merge($this->validationErrors, $errors); + return $this->validationErrors; } - - /** - * validation shortcut: maximum field length - */ - function maxLength($field, $input, $max, $msg) { - if (strlen($input) > $max) { - $this->validationErrors[$field] = $msg; - } - } - + var $hasMany_full = array(); var $hasAndBelongsToMany_full = array(); var $belongsTo_full = array(); - + function bindFully() { $this->bindModel(array('hasMany' => $this->hasMany_full, 'hasAndBelongsToMany' => $this->hasAndBelongsToMany_full, 'belongsTo' => $this->belongsTo_full)); } - - /** - * Index associations by model name. - * - * @return array $model => ($association, $definition) - */ - function _bindings() { - $assoc = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'); - $relations = array(); - foreach ($assoc as $a) { - if (isset($this->$a)) { - foreach ($this->$a as $rel => $def) { - $relations[$rel] = array($a, $def); - } - } - } - return $relations; - } - - /** - * Unbinds all models, then rebinds only the models passed as arguments. - * >>> $this->Addon->bindOnly('Users', 'Framlings') - * @param mixed [Model,...] - */ - function bindOnly() { - // Make sure all the associations are available before introspection. - $this->__resetAssociations(); - $bindings = $this->_bindings(); - $this->unbindFully(); - - $models = func_get_args(); - foreach ($models as $model) { - list($assoc, $def) = $bindings[$model]; - $this->bindModel(array($assoc => array($model => $def))); - } - } - + function unbindFully() { $unbind = array(); foreach ($this->belongsTo as $model=>$info) { @@ -411,7 +395,7 @@ class AppModel extends Model } $this->unbindModel($unbind); } - + /** * Updates a table without requiring a primary key * @param mixed $update Array of fields and values to update or the update string @@ -423,9 +407,9 @@ class AppModel extends Model if (empty($update) || empty($where)) { return false; } - + $db =& ConnectionManager::getDataSource($this->useDbConfig); - + //Create update string from array if (is_array($update)) { foreach ($update as $field => $value) { @@ -440,7 +424,7 @@ class AppModel extends Model elseif (is_string($update)) { $updateQry = $update; } - + //Create where clause from array if (is_array($where)) { foreach ($where as $field => $value) { @@ -455,9 +439,9 @@ class AppModel extends Model elseif (is_string($where)) { $whereQry = $where; } - + $limitQry = empty($limit) ? '' : " LIMIT {$limit}"; - + return $db->execute("UPDATE ".$db->name($db->fullTableName($this))." SET {$updateQry} WHERE {$whereQry}{$limitQry}"); } @@ -473,16 +457,16 @@ class AppModel extends Model $_tr_fields_tobestored = array_intersect($this->translated_fields, array_keys($this->data[$this->name])); if (empty($_tr_fields_tobestored)) return true; } - + // copy the data we intend to save $data = $this->data; // Allow querying for a locale other than currently set $lang = $this->getLang(); - + // Make sure translation ids are returned $this->translationReplace = false; - + // start a transaction $db =& ConnectionManager::getDataSource($this->useDbConfig); $this->begin(); @@ -498,7 +482,7 @@ class AppModel extends Model $errors = false; foreach ($this->translated_fields as $tr_field) { if (!isset($data[$this->name][$tr_field])) continue; - + // remove existing translation if empty $_remove = (empty($data[$this->name][$tr_field])); @@ -513,7 +497,7 @@ class AppModel extends Model unset($data[$this->name][$tr_field]); continue; } - + $_update = false; // generate a new primary key id $db->execute('UPDATE translations_seq SET id=LAST_INSERT_ID(id+1);'); @@ -548,17 +532,17 @@ class AppModel extends Model ."({$_trans_id}, '{$lang}', '{$data[$this->name][$tr_field]}', NOW());"; $_res = $db->execute($sql); } - + // errors? don't go on if ($_res === false) { $errors = true; break; } - + // replace localized string by localization id in data to be saved $data[$this->name][$tr_field] = $_trans_id; } - + // return to default $this->translationReplace = true; // if something went wrong, roll back @@ -622,7 +606,7 @@ class AppModel extends Model } return false; } - + /** * Gets translations for all locales for the specific record and fields * @param int $id the primary key to pull from @@ -635,9 +619,9 @@ class AppModel extends Model if (empty($fields) || !is_array($fields)) { $fields = $this->translated_fields; } - + $translations = array(); - + // Pull the translation ids for the selected fields $tableInfo = $this->query("SELECT ".implode($fields, ', ')." FROM {$this->table} AS {$this->name} WHERE {$this->name}.id={$id}", $cache, $cache); if (!empty($tableInfo)) { @@ -646,11 +630,11 @@ class AppModel extends Model if (!empty($translation_id)) { $translation_ids[$field] = $translation_id; } - + $translations[$field] = array(); } } - + // Pull translations for all ids if (!empty($translation_ids)) { $where = $includeNULL ? '' : ' AND Translation.localized_string IS NOT NULL'; @@ -671,7 +655,7 @@ class AppModel extends Model return $translations; } } - + if ($returnIDs) { return array($translations, $translation_ids); } @@ -679,7 +663,7 @@ class AppModel extends Model return $translations; } } - + /** * Save translations for new, updated, and deleted locales. * This should only be used for mass updating and is more efficient @@ -692,7 +676,7 @@ class AppModel extends Model // Pull all existing translations for fields to save $fields = array_keys($data); list($existing, $translation_ids) = $this->getAllTranslations($id, $fields, true, true); - + // Handle updated and deleted translations if (!empty($existing)) { foreach ($existing as $field => $translations) { @@ -707,13 +691,13 @@ class AppModel extends Model $this->execute("UPDATE translations SET localized_string='{$data[$field][$locale]}', modified=NOW() WHERE id={$translation_ids[$field]} AND locale='{$locale}'"); } // Else, no changes - + unset($data[$field][$locale]); } } } } - + // Handle new translations if (!empty($data)) { foreach ($data as $field => $translations) { @@ -741,30 +725,14 @@ class AppModel extends Model } } } - - /** - * Validate localized fields from translation box - * @param array $data unescaped translation data - * @return bool all data validated - */ - function validateTranslations($data) { - foreach ($data as $field => $translations) { - if (!in_array($field, $this->translated_fields)) continue; - foreach ($translations as $locale => $translation) { - $this->invalidFields(array($this->name => array($field => $translation))); - if (!empty($this->validationErrors)) return false; - } - } - return true; - } - + /** * Separates an array of data into localized fields and unlocalized fields */ function splitLocalizedFields($data) { $localizedFields = array(); $unlocalizedFields = array(); - + if (!empty($data)) { foreach ($data as $field => $value) { if (in_array($field, $this->translated_fields)) { @@ -775,10 +743,10 @@ class AppModel extends Model } } } - + return array($localizedFields, $unlocalizedFields); } - + /** * Strips fields that aren't in the specified whitelist */ @@ -791,9 +759,9 @@ class AppModel extends Model } } } - + return $safe; } - + } ?> |