/** * Uploads a file * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param string $method_name The method that was called * @param array $parameters The parameters passed to the method * @return fFile The uploaded file */ public static function upload($object, &$values, &$old_values, &$related_records, &$cache, $method_name, $parameters) { $class = get_class($object); list($action, $column) = fORM::parseMethod($method_name); $existing_temp_file = FALSE; // Try to upload the file putting it in the temp dir incase there is a validation problem with the record try { $upload_dir = self::$file_upload_columns[$class][$column]; $temp_dir = self::prepareTempDir($upload_dir); if (!fUpload::check($column)) { throw new fExpectedException('Please upload a file'); } $uploader = self::setUpFUpload($class, $column); $file = $uploader->move($temp_dir, $column); // If there was an eror, check to see if we have an existing file } catch (fExpectedException $e) { // If there is an existing file and none was uploaded, substitute the existing file $existing_file = fRequest::get('existing-' . $column); $delete_file = fRequest::get('delete-' . $column, 'boolean'); $no_upload = $e->getMessage() == self::compose('Please upload a file'); if ($existing_file && $delete_file && $no_upload) { $file = NULL; } elseif ($existing_file) { $file_path = $upload_dir->getPath() . $existing_file; $file = fFilesystem::createObject($file_path); $current_file = $values[$column]; // If the existing file is the same as the current file, we can just exit now if ($current_file && $file->getPath() == $current_file->getPath()) { return; } $existing_temp_file = TRUE; } else { $file = NULL; } } // Assign the file fActiveRecord::assign($values, $old_values, $column, $file); // Perform the file upload inheritance if (!empty(self::$column_inheritence[$class][$column])) { foreach (self::$column_inheritence[$class][$column] as $other_column) { if ($file) { // Image columns will only inherit if it is an fImage object if (!$file instanceof fImage && isset(self::$image_upload_columns[$class][$other_column])) { continue; } $other_upload_dir = self::$file_upload_columns[$class][$other_column]; $other_temp_dir = self::prepareTempDir($other_upload_dir); if ($existing_temp_file) { $other_file = fFilesystem::createObject($other_temp_dir->getPath() . $file->getName()); } else { $other_file = $file->duplicate($other_temp_dir, FALSE); } } else { $other_file = $file; } fActiveRecord::assign($values, $old_values, $other_column, $other_file); if (!$existing_temp_file && $other_file) { self::processImage($class, $other_column, $other_file); } } } // Process the file if (!$existing_temp_file && $file) { self::processImage($class, $column, $file); } return $file; }
/** * Handles all method calls for columns, related records and hook callbacks * * Dynamically handles `get`, `set`, `prepare`, `encode` and `inspect` * methods for each column in this record. Method names are in the form * `verbColumName()`. * * This method also handles `associate`, `build`, `count`, `has`, and `link` * verbs for records in many-to-many relationships; `build`, `count`, `has` * and `populate` verbs for all related records in one-to-many relationships * and `create`, `has` and `populate` verbs for all related records in * one-to-one relationships, and the `create` verb for all related records * in many-to-one relationships. * * Method callbacks registered through fORM::registerActiveRecordMethod() * will be delegated via this method. * * @param string $method_name The name of the method called * @param array $parameters The parameters passed * @return mixed The value returned by the method called */ public function __call($method_name, $parameters) { $class = get_class($this); if (!isset(self::$callback_cache[$class][$method_name])) { if (!isset(self::$callback_cache[$class])) { self::$callback_cache[$class] = array(); } $callback = fORM::getActiveRecordMethod($class, $method_name); self::$callback_cache[$class][$method_name] = $callback ? $callback : FALSE; } if ($callback = self::$callback_cache[$class][$method_name]) { return call_user_func_array($callback, array($this, &$this->values, &$this->old_values, &$this->related_records, &$this->cache, $method_name, $parameters)); } if (!isset(self::$method_name_cache[$method_name])) { list($action, $subject) = fORM::parseMethod($method_name); if (in_array($action, array('get', 'encode', 'prepare', 'inspect', 'set'))) { $subject = fGrammar::underscorize($subject); } else { if (in_array($action, array('build', 'count', 'inject', 'link', 'list', 'tally'))) { $subject = fGrammar::singularize($subject); } $subject = fORM::getRelatedClass($class, $subject); } self::$method_name_cache[$method_name] = array('action' => $action, 'subject' => $subject); } else { $action = self::$method_name_cache[$method_name]['action']; $subject = self::$method_name_cache[$method_name]['subject']; } switch ($action) { // Value methods case 'get': return $this->get($subject); case 'encode': if (isset($parameters[0])) { return $this->encode($subject, $parameters[0]); } return $this->encode($subject); case 'prepare': if (isset($parameters[0])) { return $this->prepare($subject, $parameters[0]); } return $this->prepare($subject); case 'inspect': if (isset($parameters[0])) { return $this->inspect($subject, $parameters[0]); } return $this->inspect($subject); case 'set': if (sizeof($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } return $this->set($subject, $parameters[0]); // Related data methods // Related data methods case 'associate': if (sizeof($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } $records = $parameters[0]; $route = isset($parameters[1]) ? $parameters[1] : NULL; list($subject, $route, $plural) = self::determineSubject($class, $subject, $route); if ($plural) { fORMRelated::associateRecords($class, $this->related_records, $subject, $records, $route); } else { fORMRelated::associateRecord($class, $this->related_records, $subject, $records, $route); } return $this; case 'build': if (isset($parameters[0])) { return fORMRelated::buildRecords($class, $this->values, $this->related_records, $subject, $parameters[0]); } return fORMRelated::buildRecords($class, $this->values, $this->related_records, $subject); case 'count': if (isset($parameters[0])) { return fORMRelated::countRecords($class, $this->values, $this->related_records, $subject, $parameters[0]); } return fORMRelated::countRecords($class, $this->values, $this->related_records, $subject); case 'create': if (isset($parameters[0])) { return fORMRelated::createRecord($class, $this->values, $this->related_records, $subject, $parameters[0]); } return fORMRelated::createRecord($class, $this->values, $this->related_records, $subject); case 'has': $route = isset($parameters[0]) ? $parameters[0] : NULL; list($subject, $route, ) = self::determineSubject($class, $subject, $route); return fORMRelated::hasRecords($class, $this->values, $this->related_records, $subject, $route); case 'inject': if (sizeof($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } if (isset($parameters[1])) { return fORMRelated::setRecordSet($class, $this->related_records, $subject, $parameters[0], $parameters[1]); } return fORMRelated::setRecordSet($class, $this->related_records, $subject, $parameters[0]); case 'link': if (isset($parameters[0])) { fORMRelated::linkRecords($class, $this->related_records, $subject, $parameters[0]); } else { fORMRelated::linkRecords($class, $this->related_records, $subject); } return $this; case 'list': if (isset($parameters[0])) { return fORMRelated::getPrimaryKeys($class, $this->values, $this->related_records, $subject, $parameters[0]); } return fORMRelated::getPrimaryKeys($class, $this->values, $this->related_records, $subject); case 'populate': $route = isset($parameters[0]) ? $parameters[0] : NULL; list($subject, $route, ) = self::determineSubject($class, $subject, $route); fORMRelated::populateRecords($class, $this->related_records, $subject, $route); return $this; case 'tally': if (sizeof($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } if (isset($parameters[1])) { return fORMRelated::setCount($class, $this->related_records, $subject, $parameters[0], $parameters[1]); } return fORMRelated::setCount($class, $this->related_records, $subject, $parameters[0]); // Error handler // Error handler default: throw new fProgrammerException('Unknown method, %s(), called', $method_name); } }
/** * Sets the money column and then tries to objectify it with an related currency column * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param string $method_name The method that was called * @param array $parameters The parameters passed to the method * @return fActiveRecord The record object, to allow for method chaining */ public static function setMoneyColumn($object, &$values, &$old_values, &$related_records, &$cache, $method_name, $parameters) { list($action, $column) = fORM::parseMethod($method_name); $class = get_class($object); if (count($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } $value = $parameters[0]; fActiveRecord::assign($values, $old_values, $column, $value); $currency_column = self::$money_columns[$class][$column]; // See if we can make an fMoney object out of the values self::objectifyMoneyWithCurrency($values, $old_values, $column, $currency_column); if ($currency_column) { if ($value instanceof fMoney) { fActiveRecord::assign($values, $old_values, $currency_column, $value->getCurrency()); } } return $object; }
/** * Allows for preloading various data related to the record set in single database queries, as opposed to one query per record * * This method will handle methods in the format `verbRelatedRecords()` for * the verbs `build`, `prebuild`, `precount` and `precreate`. * * `build` calls `create{RelatedClass}()` on each record in the set and * returns the result as a new record set. The relationship route can be * passed as an optional parameter. * * `prebuild` builds *-to-many record sets for all records in the record * set. `precount` will count records in *-to-many record sets for every * record in the record set. `precreate` will create a *-to-one record * for every record in the record set. * * @param string $method_name The name of the method called * @param string $parameters The parameters passed * @return void */ public function __call($method_name, $parameters) { if ($callback = fORM::getRecordSetMethod($method_name)) { return call_user_func_array($callback, array($this, $this->class, &$this->records, $method_name, $parameters)); } list($action, $subject) = fORM::parseMethod($method_name); $route = $parameters ? $parameters[0] : NULL; // This check prevents fGrammar exceptions being thrown when an unknown method is called if (in_array($action, array('build', 'prebuild', 'precount', 'precreate'))) { $related_class = fGrammar::singularize($subject); $related_class_sans_namespace = $related_class; if (!is_array($this->class)) { $related_class = fORM::getRelatedClass($this->class, $related_class); } } switch ($action) { case 'build': if ($route) { $this->precreate($related_class, $route); return $this->buildFromCall('create' . $related_class_sans_namespace, $route); } $this->precreate($related_class); return $this->buildFromCall('create' . $related_class_sans_namespace); case 'prebuild': return $this->prebuild($related_class, $route); case 'precount': return $this->precount($related_class, $route); case 'precreate': return $this->precreate($related_class, $route); } throw new fProgrammerException('Unknown method, %s(), called', $method_name); }
/** * Sets the timezone column and then tries to objectify the related timestamp column * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param string $method_name The method that was called * @param array $parameters The parameters passed to the method * @return fActiveRecord The record object, to allow for method chaining */ public static function setTimezoneColumn($object, &$values, &$old_values, &$related_records, &$cache, $method_name, $parameters) { list($action, $subject) = fORM::parseMethod($method_name); $column = fGrammar::underscorize($subject); $class = get_class($object); if (!isset($parameters[0])) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } fActiveRecord::assign($values, $old_values, $column, $parameters[0]); // See if we can make an fTimestamp object out of the values self::objectifyTimestampWithTimezone($values, $old_values, self::$timezone_columns[$class][$column], $column); return $object; }
/** * Returns the metadata about a column including features added by this class * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param string $method_name The method that was called * @param array $parameters The parameters passed to the method * @return mixed The metadata array or element specified */ public static function inspect($object, &$values, &$old_values, &$related_records, &$cache, $method_name, $parameters) { list($action, $subject) = fORM::parseMethod($method_name); $column = fGrammar::underscorize($subject); $class = get_class($object); $table = fORM::tablize($class); $db = fORMDatabase::retrieve($class, 'read'); $schema = fORMSchema::retrieve($class); $info = $schema->getColumnInfo($table, $column); $element = isset($parameters[0]) ? $parameters[0] : NULL; $other_columns = self::$ordering_columns[$class][$column]; // Retrieve the current max ordering index from the database $params = array("SELECT MAX(%r) FROM %r", $column, $table); if ($other_columns) { $params[0] .= " WHERE "; $params = self::addOtherFieldsWhereParams($schema, $params, $table, $other_columns, $values); } $max_value = (int) call_user_func_array($db->translatedQuery, $params)->fetchScalar(); // If this is a new record, or in a new set, we need one more space in the ordering index if (self::isInNewSet($column, $other_columns, $values, $old_values)) { $max_value += 1; } $info['max_ordering_value'] = $max_value; $info['feature'] = 'ordering'; fORM::callInspectCallbacks($class, $column, $info); if ($element) { return isset($info[$element]) ? $info[$element] : NULL; } return $info; }
/** * Returns the metadata about a column including features added by this class * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param string $method_name The method that was called * @param array $parameters The parameters passed to the method * @return mixed The metadata array or element specified */ public static function inspect($object, &$values, &$old_values, &$related_records, &$cache, $method_name, $parameters) { list($action, $column) = fORM::parseMethod($method_name); $class = get_class($object); $table = fORM::tablize($class); $info = fORMSchema::retrieve()->getColumnInfo($table, $column); $element = isset($parameters[0]) ? $parameters[0] : NULL; $column = self::$ordering_columns[$class]['column']; $other_columns = self::$ordering_columns[$class]['other_columns']; // Retrieve the current max ordering index from the database $sql = "SELECT max(" . $column . ") FROM " . $table; if ($other_columns) { $sql .= " WHERE " . self::createOtherFieldsWhereClause($table, $other_columns, $values); } $max_value = (int) fORMDatabase::retrieve()->translatedQuery($sql)->fetchScalar(); // If this is a new record, or in a new set, we need one more space in the ordering index if (self::isInNewSet($column, $other_columns, $values, $old_values)) { $max_value += 1; } $info['max_ordering_value'] = $max_value; $info['feature'] = 'ordering'; fORM::callInspectCallbacks($class, $column, $info); if ($element) { return isset($info[$element]) ? $info[$element] : NULL; } return $info; }
/** * Sets the value for an email column, trimming the value if it is a valid email * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param string $method_name The method that was called * @param array $parameters The parameters passed to the method * @return fActiveRecord The record object, to allow for method chaining */ public static function setEmailColumn($object, &$values, &$old_values, &$related_records, &$cache, $method_name, $parameters) { list($action, $subject) = fORM::parseMethod($method_name); $column = fGrammar::underscorize($subject); $class = get_class($object); if (count($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } $email = $parameters[0]; if (preg_match('#^\\s*[a-z0-9\\.\'_\\-\\+]+@(?:[a-z0-9\\-]+\\.)+[a-z]{2,}\\s*$#iD', $email)) { $email = trim($email); } if ($email === '') { $email = NULL; } fActiveRecord::assign($values, $old_values, $column, $email); return $object; }
/** * Handles all method calls for columns, related records and hook callbacks * * Dynamically handles `get`, `set`, `prepare`, `encode` and `inspect` * methods for each column in this record. Method names are in the form * `verbColumName()`. * * This method also handles `associate`, `build`, `count` and `link` verbs * for records in many-to-many relationships; `build`, `count` and * `populate` verbs for all related records in one-to-many relationships * and the `create` verb for all related records in *-to-one relationships. * * Method callbacks registered through fORM::registerActiveRecordMethod() * will be delegated via this method. * * @param string $method_name The name of the method called * @param array $parameters The parameters passed * @return mixed The value returned by the method called */ public function __call($method_name, $parameters) { $class = get_class($this); if (!isset(self::$callback_cache[$class][$method_name])) { if (!isset(self::$callback_cache[$class])) { self::$callback_cache[$class] = array(); } $callback = fORM::getActiveRecordMethod($class, $method_name); self::$callback_cache[$class][$method_name] = $callback ? $callback : FALSE; } if ($callback = self::$callback_cache[$class][$method_name]) { return call_user_func_array($callback, array($this, &$this->values, &$this->old_values, &$this->related_records, &$this->cache, $method_name, $parameters)); } if (!isset(self::$method_name_cache[$method_name])) { list($action, $subject) = fORM::parseMethod($method_name); self::$method_name_cache[$method_name] = array('action' => $action, 'subject' => $subject); } else { $action = self::$method_name_cache[$method_name]['action']; $subject = self::$method_name_cache[$method_name]['subject']; } switch ($action) { // Value methods case 'get': return $this->get($subject); case 'encode': if (isset($parameters[0])) { return $this->encode($subject, $parameters[0]); } return $this->encode($subject); case 'prepare': if (isset($parameters[0])) { return $this->prepare($subject, $parameters[0]); } return $this->prepare($subject); case 'inspect': if (isset($parameters[0])) { return $this->inspect($subject, $parameters[0]); } return $this->inspect($subject); case 'set': if (sizeof($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } return $this->set($subject, $parameters[0]); // Related data methods // Related data methods case 'associate': if (sizeof($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } $table = fORM::tablize($class); $records = $parameters[0]; $route = isset($parameters[1]) ? $parameters[1] : NULL; $plural = FALSE; // one-to-many relationships need to use plural forms if (in_array($subject, fORMSchema::retrieve()->getTables())) { if (fORMSchema::isOneToOne($table, $subject, $route)) { throw new fProgrammerException('The table %1$s is not in a %2$srelationship with the table %3$s', $table, '*-to-many ', $subject); } $subject = fGrammar::singularize($subject); $plural = TRUE; } $subject = fGrammar::camelize($subject, TRUE); // This handles one-to-many and many-to-many relationships if ($plural) { return fORMRelated::associateRecords($class, $this->related_records, $subject, $records, $route); } // This handles one-to-one relationships return fORMRelated::associateRecord($class, $this->related_records, $subject, $records, $route); case 'build': $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); if (isset($parameters[0])) { return fORMRelated::buildRecords($class, $this->values, $this->related_records, $subject, $parameters[0]); } return fORMRelated::buildRecords($class, $this->values, $this->related_records, $subject); case 'count': $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); if (isset($parameters[0])) { return fORMRelated::countRecords($class, $this->values, $this->related_records, $subject, $parameters[0]); } return fORMRelated::countRecords($class, $this->values, $this->related_records, $subject); case 'create': $subject = fGrammar::camelize($subject, TRUE); if (isset($parameters[0])) { return fORMRelated::createRecord($class, $this->values, $this->related_records, $subject, $parameters[0]); } return fORMRelated::createRecord($class, $this->values, $this->related_records, $subject); case 'inject': if (sizeof($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); if (isset($parameters[1])) { return fORMRelated::setRecordSet($class, $this->related_records, $subject, $parameters[0], $parameters[1]); } return fORMRelated::setRecordSet($class, $this->related_records, $subject, $parameters[0]); case 'link': $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); if (isset($parameters[0])) { return fORMRelated::linkRecords($class, $this->related_records, $subject, $parameters[0]); } return fORMRelated::linkRecords($class, $this->related_records, $subject); case 'list': $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); if (isset($parameters[0])) { return fORMRelated::getPrimaryKeys($class, $this->values, $this->related_records, $subject, $parameters[0]); } return fORMRelated::getPrimaryKeys($class, $this->values, $this->related_records, $subject); case 'populate': $table = fORM::tablize($class); $route = isset($parameters[0]) ? $parameters[0] : NULL; // one-to-many relationships need to use plural forms if (in_array($subject, fORMSchema::retrieve()->getTables())) { if (fORMSchema::isOneToOne($table, $subject, $route)) { throw new fProgrammerException('The table %1$s is not in a%2$srelationship with the table %3$s', $table, ' one-to-many ', $subject); } $subject = fGrammar::singularize($subject); } $subject = fGrammar::camelize($subject, TRUE); return fORMRelated::populateRecords($class, $this->related_records, $subject, $route); case 'tally': if (sizeof($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); if (isset($parameters[1])) { return fORMRelated::setCount($class, $this->related_records, $subject, $parameters[0], $parameters[1]); } return fORMRelated::setCount($class, $this->related_records, $subject, $parameters[0]); // Error handler // Error handler default: throw new fProgrammerException('Unknown method, %s(), called', $method_name); } }
/** * Prepares a number column by calling fNumber::format() * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param string $method_name The method that was called * @param array $parameters The parameters passed to the method * @return string The formatted link */ public static function prepareNumberColumn($object, &$values, &$old_values, &$related_records, &$cache, $method_name, $parameters) { list($action, $column) = fORM::parseMethod($method_name); $class = get_class($object); $column_info = fORMSchema::retrieve()->getColumnInfo(fORM::tablize($class), $column); $value = $values[$column]; if ($value instanceof fNumber) { if ($column_info['type'] == 'float') { $decimal_places = isset($parameters[0]) ? (int) $parameters[0] : $column_info['decimal_places']; if ($decimal_places !== NULL) { $value = $value->trunc($decimal_places)->format(); } else { $value = $value->format(); } } else { $value = $value->format(); } } return fHTML::prepare($value); }