/** * Edit an objects of a given CtrlClass, if an ID is given * Or renders a blank form if not * This essentially renders a form for the object * @param integer $ctrl_class_id The ID of the class we're editing * @param integer $object_id The ID of the object we're editing (zero to create a new one) * @param string $filter_string Optional list filter; such as 43,1, which will set the value of the ctrl_property 43 to 1 when we save the form * * @return Response */ public function edit_object($ctrl_class_id, $object_id = NULL, $filter_string = NULL) { // Convert the the $filter parameter into one that makes sense // Used when linking BACK to a list $filter_array = $this->convert_filter_string_to_array($filter_string); $default_values = $this->convert_filter_string_to_array($filter_string); // Note that we use this to set default values, not filter the list $default_description = $this->describe_filter($default_values); $object = $this->get_object_from_ctrl_class_id($ctrl_class_id, $object_id); $ctrl_class = CtrlClass::where('id', $ctrl_class_id)->firstOrFail(); $ctrl_properties = $ctrl_class->ctrl_properties()->where('fieldset', '!=', '')->get(); $tabbed_form_fields = []; $hidden_form_fields = []; foreach ($ctrl_properties as $ctrl_property) { unset($value); // Reset $value , $values $values = []; // Adjust the field type, mainly to handle relationships and multiple dropdowns /* Or, do we actually handle this in the dropdown.blade template? Currently, yes we do: if ($ctrl_property->field_type == 'dropdown' && $ctrl_property->relationship_type == 'belongsToMany') { $ctrl_property->field_type = 'dropdown_multiple'; } */ // We do use the same view for image and file, though: if (in_array($ctrl_property->field_type, ['image', 'file'])) { $ctrl_property->template = 'krajee'; } elseif (in_array($ctrl_property->field_type, ['date', 'datetime'])) { $ctrl_property->template = 'date'; } else { $ctrl_property->template = $ctrl_property->field_type; } if (!view()->exists('ctrl::form_fields.' . $ctrl_property->template)) { trigger_error("Cannot load view for field type " . $ctrl_property->field_type); } // Ascertain the name current value of this field // This essentially converts 'one' to 'one_id' and so on $field_name = $ctrl_property->get_field_name(); if ($ctrl_property->related_to_id && in_array($ctrl_property->relationship_type, ['hasMany', 'belongsToMany'])) { $related_objects = $object->{$field_name}; $value = []; foreach ($related_objects as $related_object) { $value[$related_object->id] = $this->get_object_title($related_object); } } else { // Do we have a default value set in the querystring? if ($default_values && !$object_id) { // We're adding a new object foreach ($default_values as $default_value) { if ($ctrl_property->id == $default_value['ctrl_property_id']) { $value = $default_value['value']; } } } if (!isset($value)) { // No default value, so pull it from the existing object $value = $object->{$field_name}; } } // Do we have a range of valid values for this field? For example, an ENUM or relationship field if ($ctrl_property->related_to_id) { $related_ctrl_class = \Sevenpointsix\Ctrl\Models\CtrlClass::find($ctrl_property->related_to_id); $related_class = $related_ctrl_class->get_class(); // This breaks as we have too many related items... but we load these via Ajax anyway if there are more than 20. So, just get the first 20... // dump($related_class); // $related_objects = $related_class::all(); // $related_objects = $related_class::take(21)->get(); // This needs an overhaul, can we chunk for now? /* No, doesn't work, times out $related_class::chunk(200, function ($related_objects) { foreach ($related_objects as $related_object) { $values[$related_object->id] = $this->get_object_title($related_object); } }); */ /* foreach ($related_objects as $related_object) { $values[$related_object->id] = $this->get_object_title($related_object); } */ // If we use select2 for EVERYTHING (sensible I think?), we can just do this...update template/dropdown accordingly if (!empty($value)) { $related_objects = $related_class::where('id', $value)->get(); foreach ($related_objects as $related_object) { $values[$related_object->id] = $this->get_object_title($related_object); } } } else { $column = DB::select("SHOW COLUMNS FROM {$ctrl_property->ctrl_class->table_name} WHERE Field = '{$ctrl_property->name}'"); if (!isset($column[0])) { dump("SHOW COLUMNS FROM {$ctrl_property->ctrl_class->table_name} WHERE Field = '{$ctrl_property->name}'"); dd($column); } $type = $column[0]->Type; // Is this an ENUM field? preg_match("/enum\\((.*)\\)/", $type, $matches); if ($matches) { // Convert 'One','Two','Three' into an array $enums = explode("','", trim($matches[1], "'")); $loop = 1; foreach ($enums as $enum) { // Note that apostrophes are doubled-up when exported from SHOW COLUMNS $value = str_replace("''", "'", $enum); $values[$loop++] = $value; } } } // Build the form_field anddd it to the tabs $tab_name = $ctrl_property->fieldset; $tab_icon = 'fa fa-list'; $tab_text = ''; if (!isset($tabbed_form_fields[$tab_name])) { $tabbed_form_fields[$tab_name] = ['icon' => $tab_icon, 'text' => $tab_text, 'form_fields' => []]; } $tabbed_form_fields[$tab_name]['form_fields']['form_id_' . $ctrl_property->name] = ['id' => 'form_id_' . $ctrl_property->name, 'name' => $field_name, 'values' => $values, 'value' => $value, 'type' => $ctrl_property->field_type, 'template' => $ctrl_property->template, 'label' => $ctrl_property->label, 'tip' => $ctrl_property->tip, 'related_ctrl_class_name' => !empty($related_ctrl_class) ? $related_ctrl_class->name : false]; /* Note: we pass in the related_ctrl_class so that we can use Ajax to generate the list of select2 options. Otherwise, if we're working with (eg) Sogra Products, we have a select box with thousands of options, which breaks. */ } // TODO: right, we need to add something here that allows us to customise the list of form fields // I think we need to use a serviceprovider and inject it into this main controlller // See the comment on this page re. ReportingService: http://stackoverflow.com/questions/30365169/access-controller-method-from-another-controller-in-laravel-5 if ($this->module->enabled('manipulate_form')) { $tabbed_form_fields = $this->module->run('manipulate_form', [$tabbed_form_fields, $ctrl_class_id, $object_id, $filter_string]); } // Add any filter properties as hidden fields if ($default_values) { // Set HIDDEN fields here; we can default known fields in the main loop above foreach ($default_values as $default_value) { $default_property = $ctrl_class->ctrl_properties()->where('fieldset', '')->where('id', $default_value['ctrl_property_id'])->first(); if ($default_property !== null) { $default_field_name = $default_property->get_field_name(); $hidden_form_fields[] = ['id' => 'form_id_' . $default_field_name, 'name' => $default_field_name, 'value' => $default_value['value']]; } } } if ($object_id) { $page_title = 'Edit this ' . $ctrl_class->get_singular(); // $page_description = '“'.$object->title.'”'; $page_description = '“' . $this->get_object_title($object) . '”'; $delete_link = route('ctrl::delete_object', [$ctrl_class->id, $object->id]); } else { $page_title = 'Add ' . $this->a_an($ctrl_class->get_singular()) . ' ' . $ctrl_class->get_singular(); $page_description = $default_description ? '…' . $default_description : ''; $delete_link = ''; } // If we've set default values here, then that implies that we came through a filtered list; and we want to go back to THAT list, not a list of all these items // Or do we...? Hmmm. It might make more sense to return to a filtered list of *these* items... TBC. /* Yes, use the code below from @list_objects $back_link = route('ctrl::list_objects',[$ctrl_class->id,$filter_string]); */ // Do we have an unfiltered list we can link back to? if ($filter_array) { // dd($filter_array); // $filter_array[0]['ctrl_property_id'] is now the ID of the property that links back to the "parent" list, so: $unfiltered_ctrl_property = CtrlProperty::where('id', $filter_array[0]['ctrl_property_id'])->firstOrFail(); $unfiltered_ctrl_class = CtrlClass::where('id', $unfiltered_ctrl_property->related_to_id)->firstOrFail(); $back_link = route('ctrl::list_objects', [$unfiltered_ctrl_class->id]); } else { // Is this a sensible fallback? $back_link = route('ctrl::list_objects', [$ctrl_class->id, $filter_string]); } // Similarly... once we've saved a filtered object, we want to bounce back to a filtered list. This enables it: $save_link = route('ctrl::save_object', [$ctrl_class->id, $object_id, $filter_string]); return view('ctrl::edit_object', ['ctrl_class' => $ctrl_class, 'page_title' => $page_title, 'page_description' => $page_description, 'back_link' => $back_link, 'delete_link' => $delete_link, 'save_link' => $save_link, 'object' => $object, 'tabbed_form_fields' => $tabbed_form_fields, 'hidden_form_fields' => $hidden_form_fields]); }
/** * Generate model files based on the ctrl_tables * * @return Response */ public function generate_model_files() { $model_folder = 'Ctrl/Models/'; if (!File::exists(app_path($model_folder))) { File::makeDirectory(app_path($model_folder), 0777, true); // See http://laravel-recipes.com/recipes/147/creating-a-directory } else { // Otherwise, empty the folder: File::cleanDirectory(app_path($model_folder)); } $ctrl_classes = \Sevenpointsix\Ctrl\Models\CtrlClass::get(); foreach ($ctrl_classes as $ctrl_class) { $view_data = ['model_name' => $ctrl_class->name, 'soft_deletes' => false, 'table_name' => $ctrl_class->table_name, 'fillable' => [], 'belongsTo' => [], 'hasMany' => [], 'belongsToMany' => []]; // NOTE: this may need to include properties that we set using a filter in the URL // ie, if we want to add a course to a client, but "client" isn't directly visible in the form; // instead, we get to the list of courses by clicking the filtered_list "courses" when listing clients. $fillable_properties = $ctrl_class->ctrl_properties()->where('fieldset', '!=', '')->where(function ($query) { $query->whereNull('relationship_type')->orWhere('relationship_type', 'belongsTo'); })->get(); // We can only fill relationships if they're belongsTo (ie, have a specific local key, such as one_id) // OR if they're belongsToMany, in which case we have a pivot table (I think?) foreach ($fillable_properties as $fillable_property) { $view_data['fillable'][] = $fillable_property->get_field_name(); // Does Laravel/Eloquent give us a quick way of extracting all ->name properties into an array? // I think it does. } // Which properties can be automatically filled via a filtered list? ie, clicking to add a related page to a pagesection, should set the pagesection variable. // This is a bit complex as we have to look at properties of other classes, linking to this class... $filtered_list_properties = \Sevenpointsix\Ctrl\Models\CtrlProperty::whereRaw('(find_in_set(?, flags))', ['filtered_list'])->where('related_to_id', $ctrl_class->id)->get(); if (!$filtered_list_properties->isEmpty()) { foreach ($filtered_list_properties as $filtered_list_property) { $default_properties = $ctrl_class->ctrl_properties()->where('relationship_type', 'belongsTo')->where('related_to_id', $filtered_list_property->ctrl_class_id)->get(); if (!$default_properties->isEmpty()) { foreach ($default_properties as $default_property) { $view_data['fillable'][] = $default_property->get_field_name(); } } } } $relationship_properties = $ctrl_class->ctrl_properties()->whereNotNull('related_to_id')->get(); foreach ($relationship_properties as $relationship_property) { $related_ctrl_class = \Sevenpointsix\Ctrl\Models\CtrlClass::find($relationship_property->related_to_id); $relationship_data = ['name' => $relationship_property->name, 'model' => $related_ctrl_class->name, 'foreign_key' => $relationship_property->foreign_key, 'local_key' => $relationship_property->local_key]; if ($relationship_property->relationship_type == 'belongsToMany') { $relationship_data['pivot_table'] = $relationship_property->pivot_table; } $view_data[$relationship_property->relationship_type][] = $relationship_data; } $model_code = View::make('ctrl::model_template', $view_data)->render(); $model_path = app_path($model_folder . $ctrl_class->name . '.php'); File::put($model_path, $model_code); } $this->info($ctrl_classes->count() . ' files generated'); }