I am trying to create a file upload on cakephp, I haven't been able to find any decent tutorials for cakephp 3.0 that go in detail, and I don't understand how to install plugins.
I have this in my add section
echo $this->Form->create('filename', array('action' => 'upload', 'type' => 'file'));
echo $this->Form->file('filename');
I haven't added anything in the controller yet
/**
* Index method
*
* #return void
*/
public function index()
{
$this->paginate = [
'contain' => ['Courses']
];
$this->set('contents', $this->paginate($this->Contents));
$this->set('_serialize', ['contents']);
}
/**
* View method
*
* #param string|null $id Content id.
* #return void
* #throws \Cake\Network\Exception\NotFoundException When record not found.
*/
public function view($id = null)
{
$content = $this->Contents->get($id, [
'contain' => ['Courses']
]);
$this->set('content', $content);
$this->set('_serialize', ['content']);
}
/**
* Add method
*
* #return void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$content = $this->Contents->newEntity();
if ($this->request->is('post')) {
$content = $this->Contents->patchEntity($content, $this->request->data);
if ($this->Contents->save($content)) {
$this->Flash->success('The content has been saved.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The content could not be saved. Please, try again.');
}
}
$courses = $this->Contents->Courses->find('list', ['limit' => 200]);
$this->set(compact('content', 'courses'));
$this->set('_serialize', ['content']);
}
/**
* Edit method
*
* #param string|null $id Content id.
* #return void Redirects on successful edit, renders view otherwise.
* #throws \Cake\Network\Exception\NotFoundException When record not found.
*/
public function edit($id = null)
{
$content = $this->Contents->get($id, [
'contain' => []
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$content = $this->Contents->patchEntity($content, $this->request->data);
if ($this->Contents->save($content)) {
$this->Flash->success('The content has been saved.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The content could not be saved. Please, try again.');
}
}
$courses = $this->Contents->Courses->find('list', ['limit' => 200]);
$this->set(compact('content', 'courses'));
$this->set('_serialize', ['content']);
}
/**
* Delete method
*
* #param string|null $id Content id.
* #return void Redirects to index.
* #throws \Cake\Network\Exception\NotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$content = $this->Contents->get($id);
if ($this->Contents->delete($content)) {
$this->Flash->success('The content has been deleted.');
} else {
$this->Flash->error('The content could not be deleted. Please, try again.');
}
return $this->redirect(['action' => 'index']);
}
but after this no idea what to do.
First of all, you need to decide on WHEN you're going to handle uploads. I've managed to create a dirty (but working) approach using beforeMarshal method and afterSave method (I'll explain why these two at the end).
If you create your file input like:
<?= $this->Form->file('submittedfile', ['class' => 'form-control input-upload', 'style' => 'height:100px']) ?>
or for hasMany association:
<?= $this->Form->file('images.'.$i.'.submittedfile', ['class' => 'form-control input-upload', 'style' => 'height:100px']) ?>
and you define the right associations:
$this->hasMany('Images', [
'foreignKey' => 'model_id'
]);
you could process those files before the Entity gets patched and saved:
public function beforeMarshal(Event $event, \ArrayObject $data, \ArrayObject $options) {
$images = array();
$dir = md5(time().$data['name']);
for ($i = 0; $i < count($data['images']); $i++) {
$image = $data['images'][$i]['submittedfile'];
if (!empty($image['name'])) {
if(!isset($data['id'])) {
$data['temp_dir'] = $dir;
}
else {
$dir = $data['id'];
}
if ($this->Images->uploadFile(array('img', 'model', $dir), $image) === true) {
$images[] = array('name' => pathinfo($image['name'], PATHINFO_FILENAME), 'ext' => pathinfo($image['name'], PATHINFO_EXTENSION));
}
}
}
$data['images'] = $images;
}
This is of course an example. I've decided to check, if there's an ID property set on the Entity (like for edit), because if it's not (like for create), you have to somehow identify the right path.
Here you've got a file uploading function:
public function uploadDir($path = array()) {
return $this->wwwRoot . implode(DS, $path);
}
public function uploadFile($path = array(), $filetoupload = null) {
if (!$filetoupload) {
return false;
}
$dir = new Folder($this->uploadDir($path), true, 755);
$tmp_file = new File($filetoupload['tmp_name']);
if (!$tmp_file->exists()) {
return false;
}
$file = new File($dir->path . DS . $filetoupload['name']);
if (!$tmp_file->copy($dir->pwd() . DS . $filetoupload['name'])) {
return false;
}
$file->close();
$tmp_file->delete();
return true;
}
If you added your images while there was no subdirectory with main entity ID, you have to rename the directory as soon as you get the ID:
public function afterSave(Event $event, Entity $entity, \ArrayObject $options) {
if(!empty($entity->temp_dir)) {
$this->Images->renameFolder(array('img', 'model', $entity->temp_dir),$entity->id);
}
}
calling:
public function renameFolder($path = array(), $newName) {
$oldPath = $this->wwwRoot . implode(DS, $path);
$nameToChange = array_pop($path);
array_push($path, $newName);
$newPath = $this->wwwRoot . implode(DS, $path);
return rename($oldPath, $newPath);
}
Using beforeMarshal you're able to inject your file data into Entity structure before the whole Entity is ready for saving (with associations).
Using afterSave you're able to identify the main object ID and call the set of objects you've uploaded before.
Remember to set recursive rights for saving files onto the directory, as well as rights for creating and renaming directories.
I tried to use the TinyMce plugin in cakephp but the editor is not loading but the helpers are. Even after extracting the files to the plugins folder and adding the codes in bootstrap.php file its not working.
Helper:
<?php
App::uses('AppHelper', 'View/Helper');
class TinymceHelper extends AppHelper {
// Take advantage of other helpers
public $helpers = array('Js', 'Html', 'Form');
// Check if the tiny_mce.js file has been added or not
public $_script = false;
/**
* Adds the tiny_mce.js file and constructs the options
*
* #param string $fieldName Name of a field, like this "Modelname.fieldname"
* #param array $tinyoptions Array of TinyMCE attributes for this textarea
* #return string JavaScript code to initialise the TinyMCE area
*/
function _build($fieldName, $tinyoptions = array()){
if(!$this->_script){
// We don't want to add this every time, it's only needed once
$this->_script = true;
$this->Html->script('tiny_mce/tiny_mce', array('inline' => false));
}
// Ties the options to the field
$tinyoptions['mode'] = 'exact';
$tinyoptions['elements'] = $this->domId($fieldName);
// List the keys having a function
$value_arr = array();
$replace_keys = array();
foreach($tinyoptions as $key => &$value){
// Checks if the value starts with 'function ('
if(strpos($value, 'function(') === 0){
$value_arr[] = $value;
$value = '%' . $key . '%';
$replace_keys[] = '"' . $value . '"';
}
}
// Encode the array in json
$json = $this->Js->object($tinyoptions);
// Replace the functions
$json = str_replace($replace_keys, $value_arr, $json);
$this->Html->scriptStart(array('inline' => false));
echo 'tinyMCE.init(' . $json . ');';
$this->Html->scriptEnd();
}
/**
* Creates a TinyMCE textarea.
*
* #param string $fieldName Name of a field, like this "Modelname.fieldname"
* #param array $options Array of HTML attributes.
* #param array $tinyoptions Array of TinyMCE attributes for this textarea
* #param string $preset
* #return string An HTML textarea element with TinyMCE
*/
function textarea($fieldName, $options = array(), $tinyoptions = array(), $preset = null){
// If a preset is defined
if(!empty($preset)){
$preset_options = $this->preset($preset);
// If $preset_options && $tinyoptions are an array
if(is_array($preset_options) && is_array($tinyoptions)){
$tinyoptions = array_merge($preset_options, $tinyoptions);
}else{
$tinyoptions = $preset_options;
}
}
return $this->Form->textarea($fieldName, $options) . $this->_build($fieldName, $tinyoptions);
}
/**
* Creates a TinyMCE textarea.
*
* #param string $fieldName Name of a field, like this "Modelname.fieldname"
* #param array $options Array of HTML attributes.
* #param array $tinyoptions Array of TinyMCE attributes for this textarea
* #return string An HTML textarea element with TinyMCE
*/
function input($fieldName, $options = array(), $tinyoptions = array(), $preset = null){
// If a preset is defined
if(!empty($preset)){
$preset_options = $this->preset($preset);
// If $preset_options && $tinyoptions are an array
if(is_array($preset_options) && is_array($tinyoptions)){
$tinyoptions = array_merge($preset_options, $tinyoptions);
}else{
$tinyoptions = $preset_options;
}
}
$options['type'] = 'textarea';
return $this->Form->input($fieldName, $options) . $this->_build($fieldName, $tinyoptions);
}
/**
* Creates a preset for TinyOptions
*
* #param string $name
* #return array
*/
private function preset($name){
// Full Feature
if($name == 'full'){
return array(
'theme' => 'advanced',
'plugins' => 'safari,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template',
'theme_advanced_buttons1' => 'save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect,fontselect,fontsizeselect',
'theme_advanced_buttons2' => 'cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor',
'theme_advanced_buttons3' => 'tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen',
'theme_advanced_buttons4' => 'insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,pagebreak',
'theme_advanced_toolbar_location' => 'top',
'theme_advanced_toolbar_align' => 'left',
'theme_advanced_statusbar_location' => 'bottom',
'theme_advanced_resizing' => true,
'theme_advanced_resize_horizontal' => false,
'convert_fonts_to_spans' => true,
'file_browser_callback' => 'ckfinder_for_tiny_mce'
);
}
// Basic
if($name == 'basic'){
return array(
'theme' => 'advanced',
'plugins' => 'safari,advlink,paste',
'theme_advanced_buttons1' => 'code,|,copy,pastetext,|,bold,italic,underline,|,link,unlink,|,bullist,numlist',
'theme_advanced_buttons2' => '',
'theme_advanced_buttons3' => '',
'theme_advanced_toolbar_location' => 'top',
'theme_advanced_toolbar_align' => 'center',
'theme_advanced_statusbar_location' => 'none',
'theme_advanced_resizing' => false,
'theme_advanced_resize_horizontal' => false,
'convert_fonts_to_spans' => false
);
}
// Simple
if($name == 'simple'){
return array(
'theme' => 'simple',
);
}
// BBCode
if($name == 'bbcode'){
return array(
'theme' => 'advanced',
'plugins' => 'bbcode',
'theme_advanced_buttons1' => 'bold,italic,underline,undo,redo,link,unlink,image,forecolor,styleselect,removeformat,cleanup,code',
'theme_advanced_buttons2' => '',
'theme_advanced_buttons3' => '',
'theme_advanced_toolbar_location' => 'top',
'theme_advanced_toolbar_align' => 'left',
'theme_advanced_styles' => 'Code=codeStyle;Quote=quoteStyle',
'theme_advanced_statusbar_location' => 'bottom',
'theme_advanced_resizing' => true,
'theme_advanced_resize_horizontal' => false,
'entity_encoding' => 'raw',
'add_unload_trigger' => false,
'remove_linebreaks' => false,
'inline_styles' => false
);
}
return null;
}
}
I used the following codes in my view file:
<?php
echo $this->Html->script('/TinyMCE/js/tiny_mce/tiny_mce.js', array(
'inline' => false)); ?>
<script type="text/javascript">
tinyMCE.init({
theme : "advanced",
mode : "textareas",
convert_urls : false,
theme_advanced_buttons1 : "bold,italic,underline,blockquote,separator,strikethrough,justifyleft,justifycenter,justifyright, justifyfull,bullist,numlist,undo,redo,link,unlink",
theme_advanced_buttons2: "",
theme_advanced_buttons3: "",
theme_advanced_buttons4: "",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left"
});
</script>
I added this code in my controller:
public $helpers = array('Tinymce');
I have solved it... I just inserted it manually and the problem was in setting the path to the js file.
I am trying to make a login by getting/authorizing only one input *user_number* (Not username - password).
I made my current login page with the following way:
Cakephp2.x simple login
Any help plz!
Keep it simple
If you only have one way of identifying users, the simplest (and therefore recommended) way to identify users would be to define your own login function. e.g.:
public function login() {
if ($this->request->is('post')) {
$number = $this->request->data['User']['user_number'];
$user = $this->User->findByUserNumber($number);
if ($user && $this->Auth->login($user)) {
return $this->redirect($this->Auth->redirectUrl());
} else {
$this->Session->setFlash(__('User %d doesn\'t exist', $number), 'default', array(), 'auth');
}
}
}
Note that this varies very little from the standard way of logging a user in with Cake 2.x
Create a Custom Authentication object
Create a Custom Authentication object that authenticates uses by user-number only;
Creating Custom Authentication objects
app/Controller/Component/Auth/UserNumberAuthenticate.php
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
class UserNumberAuthenticate extends BaseAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
$userModel = $this->settings['userModel'];
list($plugin, $model) = pluginSplit($userModel);
$fields = $this->settings['fields'];
if (
empty($request->data[$model])
|| empty($request->data[$model][$fields['username']])
) {
return false;
}
return $this->_findUser($request->data[$model][$fields['username']]);
}
/**
* Find a user record via his user-number/identifier
*
* #param string $usernumber The user-number/identifier.
* #return Mixed Either false on failure, or an array of user data.
*/
protected function _findUser($usernumber) {
$userModel = $this->settings['userModel'];
list($plugin, $model) = pluginSplit($userModel);
$fields = $this->settings['fields'];
$conditions = array(
$model . '.' . $fields['username'] => $usernumber,
);
if (!empty($this->settings['scope'])) {
$conditions = array_merge($conditions, $this->settings['scope']);
}
$result = ClassRegistry::init($userModel)->find('first', array(
'conditions' => $conditions,
'recursive' => $this->settings['recursive'],
'contain' => $this->settings['contain'],
));
if (empty($result) || empty($result[$model])) {
return false;
}
$user = $result[$model];
unset($result[$model]);
return array_merge($user, $result);
}
}
Then specify that you want to use your custom authentication object
Inside your AppController:
public $components = array(
'Auth' => array(
'authenticate' => array(
'UserNumber' => array(
'userModel' => 'User',
'fields' => array('username' => 'user_number')
)
)
)
);
I'm using CakePHP 2.x, and running it locally on IIS. I'm following the tutorial on Exporting data to CSV the CakePHP way, and I'm getting an error.
The URL I'm entering is like: http://myproject.localhost/territory/spreadsheet.csv
I have Router::parseExtensions('csv'); as the first thing in app\Config\routes.php
Here's my controller:
`class TerritoryController extends AppController
{
public $useTable = 'ezrep_territory';
public $paginate = array('limit' => 50);
public function beforeFilter()
{
parent::beforeFilter();
$this->Auth->deny('index');
}
public function Index()
{
// ... snip ...
}
public function Spreadsheet()
{
$data = $this->Territory->find(
'all',
array(
'conditions' => array('Territory.ez' => $this->ez),
'fields' => array('territory','terrcode','terrdesc'),
'contain' => false
));
$headers = array(
'Territory'=>array(
'territory' => 'Territory ID',
'terrcode' => 'Terr Code',
'terrdesc' => 'Terr Desc'
)
);
array_unshift($data,$headers);
$this->set(compact('data'));
}
}
`
In app\View\Layouts\csv, I have a file default.ctp:
<?php
echo $content_for_layout;
?>
And in app\View\Territory, I have a file spreadsheet.ctp:
// Loop through the data array
foreach ($data as $row)
{
// Loop through every value in a row
foreach ($row['Territory'] as &$value)
{
// Apply opening and closing text delimiters to every value
$value = "\"".$value."\"";
}
// Echo all values in a row comma separated
echo implode(",",$row['Territory'])."\n";
}
When I go to http://myproject.localhost/territory/spreadsheet.csv, I get a page that appears to be rendered through the app\View\Layouts\default.ctp with an error:
View file "C:\zproj\ezrep40\app\View\Territory\csv\spreadsheet.ctp" is missing.
and
Error: An Internal Error Has Occurred.
What am I doing wrong?
This is not right
And in app\View\Territory, I have a file spreadsheet.ctp:
you'll need to place also the view, and not just the layout, in a sub directory named after the extension:
app\View\Territory\csv\spreadsheet.ctp