Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/site/app/app_model.php
diff options
context:
space:
mode:
Diffstat (limited to 'site/app/app_model.php')
-rw-r--r--site/app/app_model.php246
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;
}
-
+
}
?>