SilverStripe 4 - Save uploaded file as Member extension property - php

I'm trying to save an uploaded file via AJAX request to a page controller, as a Member extension property.
I got a Member DataExtension that implements a File property with a many_many relation, like this:
My Member Extension
[...]
/**
* Classe Utente - Estensione
*/
class UtenteExtension extends DataExtension
{
// Dichiarazione Proprietà
private static $many_many = [
'AllegatiUpload' => File::class, // this field should save users file uploads
[...]
];
private static $owns = [
'AllegatiUpload',
[...]
];
[...]
The Controller involved:
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\ErrorPage;
use SilverStripe\Assets\Folder;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Upload;
use SilverStripe\Security;
use SilverStripe\Security\Group;
use SilverStripe\Assets\Storage\AssetStore;
/**
* Classe Controller Utente - Dashboard
*/
class UtenteDashboardController extends PageController
{
// Dichiarazione Proprietà
private static $allowed_actions = [
'carica'
];
/**
* Funzione gestione validazione ed invio form caricamento
* #param HTTPRequest $request Richiesta HTTP
* #return HTTPResponse Risposta HTTP
*/
public function carica(SS_HTTPRequest $request) {
if (!$request->isAjax()) {
return ErrorPage::response_for(404);
} else if (!$request->isPOST()) {
return $this->httpError(400, 'Metodo POST richiesto');
} else {
$documento = $request->postVar('documento'); // File data sent by FormData JS API
if (!isset($documento)) {
return $this->httpError(500, 'Dati richiesti mancanti');
} else {
// User check
$utente = Security::getCurrentUser();
if ($utente->inGroup(Group::get()->filter('Code', 'clienti')->first()->ID)) {
// File uploading
$cartellaUpload = 'clienti/'. strtolower($utente->Surname) .'/uploads';
Folder::find_or_make($cartellaUpload);
// I tried with the official guide approach, with this line - with no results
//$utente->AllegatiUpload()->setFromLocalFile($cartellaUpload, documento, AssetStore::CONFLICT_OVERWRITE);
// Then I tried with this approach
$file = File::create();
$upload = Upload::create();
$upload->loadIntoFile($documento, $file, $cartellaUpload);
// Upload check
if ($upload->isError()) {
return $this->httpError(400, 'Errore di caricamento file');
} else {
// Relate the file to the destinaion user field
$utente->{'AllegatiUpload.ID'} = $upload->getFile()->ID;
// Update user
$utente->write();
return new HTTPResponse;
}
} else {
return $this->httpError(401, 'Utente non abilitato');
}
}
}
}
}
I'm stuck in the process in order to pair the file with the correct Member. I think I missing the correct syntax, because I'm working with a many_many relation and this one doesn't fit it:
// Relate the file to the destinaion user field
utente->{'AllegatiUpload.ID'} = $upload->getFile()->ID;
I tried too with an approach like this:
$allegato = $utente->AllegatiUpload();
$allegato->setField('Documento', $upload->getFile()->ID);
$allegato->write();
But I receive this exception:
[Emergency] Uncaught BadMethodCallException: Object->__call(): the method 'write' does not exist on 'SilverStripe\ORM\ManyManyList'
Maybe I must convert AllegatiUpload to a DataObject first, then adding a File property inside it in order to avoid this?

You add new items to relations with the $object->RelationName()->add($item) syntax, in your case:
$utente->AllegatiUpload()->add($file);
reference https://docs.silverstripe.org/en/4/developer_guides/model/relations/#adding-relations

Related

Owncloud 8.1 "myapp" sample, add a function to write to a file

On Owncloud 8.1 using the owncloud command line, I create a new test app:
ocdev startapp MyApp --email mail#example.com --author "Your Name" --description "My first app" --owncloud 8
The app is working, I can add it in the owncloud control panel.
Now I'd like to write to a file, so I use one example from the owncloud documentation:
https://doc.owncloud.org/server/8.1/developer_manual/app/filesystem.html
[Edit] I started over and now, I don't know if I omitted something, but "myapp" comes with no "application.php" file.
So I create it at /var/www/core/apps/myapp/appinfo/application.php :
<?php
namespace OCA\MyApp\AppInfo;
use \OCP\AppFramework\App;
use \OCA\MyApp\Storage\AuthorStorage;
class Application extends App {
public function __construct(array $urlParams=array()){
parent::__construct('myapp', $urlParams);
$container = $this->getContainer();
/**
* Storage Layer
*/
$container->registerService('AuthorStorage', function($c) {
return new AuthorStorage($c->query('RootStorage'));
});
$container->registerService('RootStorage', function($c) {
return $c->query('ServerContainer')->getRootFolder();
});
}
}
Then I create a file called /var/www/core/apps/myapp/storage/AuthorStorage.php with:
<?php
namespace OCA\MyApp\Storage;
class AuthorStorage {
private $storage;
public function __construct($storage){
$this->storage = $storage;
}
public function writeTxt($content) {
// check if file exists and write to it if possible
try {
try {
$file = $this->storage->get('/myfile.txt');
} catch(\OCP\Files\NotFoundException $e) {
$this->storage->touch('/myfile.txt');
$file = $this->storage->get('/myfile.txt');
}
// the id can be accessed by $file->getId();
$file->putContent($content);
} catch(\OCP\Files\NotPermittedException $e) {
// you have to create this exception by yourself ;)
throw new StorageException('Cant write to file');
}
}
}
The sample app already gives me a route to the index function in the pagecontroller.php
['name' => 'page#index', 'url' => '/', 'verb' => 'GET']
How do I call the function "writeTxt" from there?
Based on http://php.net/manual/en/language.oop5.basic.php
I tried:
use \OCA\MyApp\Storage\AuthorStorage;
and
public function index() {
//added part
$a = new AuthorStorage();
$a->writeTxt('test');
//original part
$params = ['user' => $this->userId];
return new TemplateResponse('myapp', 'main', $params); //templates/main.php
}
After running I get a "Class 'OCA\MyApp\Storage\AuthorStorage' not found at /var/www/core/apps/myapp/controller/pagecontroller.php#44"
Even with the help of use \OCA\MyApp\Storage\AuthorStorage; ( ClassNotFoundException: Attempted to load class... Symfony ) it doesn't seem to help.
Thanks
Time has gone by, so I'm posting an answer to my question for owncloud 9.
Here are the steps to a basic script with read, write, copy file ability.
"application.php" has definitively been removed from the example. It was not a bug.
Following:
https://doc.owncloud.org/server/9.0/developer_manual/app/startapp.html
Generate the demo "MyApp" app:
ocdev startapp MyApp --email mail#example.com --author "Your Name" --description "My first app" --owncloud 9
Edit the file myapp/controller/pagecontroller.php
<?php
/**
* ownCloud - myapp
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* #author Your Name <mail#example.com>
* #copyright Your Name 2016
*/
namespace OCA\MyApp\Controller;
use OCP\IRequest;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Controller;
use OCP\Files\IRootFolder;
use OC\Files\Storage\Temporary;
class PageController extends Controller {
private $userId;
private $storage;
private $userstorage;
public function __construct($AppName, IRequest $request, IRootFolder $storage, $UserId){
parent::__construct($AppName, $request);
$this->storage = $storage;
$this->userId = $UserId;
$this->userstorage = $this->storage->get($this->userId.'/files/');
}
/**
* CAUTION: the #Stuff turns off security checks; for this page no admin is
* required and no CSRF check. If you don't know what CSRF is, read
* it up in the docs or you might create a security hole. This is
* basically the only required method to add this exemption, don't
* add it to any other method if you don't exactly know what it does
*
* #NoAdminRequired
* #NoCSRFRequired
*/
public function index() {
$listedudossier = $this->userstorage->getDirectoryListing();
//list all items in the root directory
//and copies the files in an directory called old
//the directory "old" is not script created
foreach ($listedudossier as $value) {
if ( $value->getType() == 'file' ){
$value->copy($this->userId.'/files/old/'.$value->getName());
//also works
//$value->copy($this->userstorage->getPath().'/old/'.$value->getName());
}
}
$params = ['listedudossier' => $listedudossier ];
return new TemplateResponse('myapp', 'main', $params); // templates/main.php
}
/**
* Simply method that posts back the payload of the request
* #NoAdminRequired
*/
public function doEcho($echo) {
//creates a file
$this->userstorage->newFile('myfile2.txt');
//opens a file, adds inputbox content and saves the file
$file = $this->userstorage->get('myfile.txt');
$contenu = $file->getContent();
$file->putContent($contenu.$echo);
return new DataResponse(['echo' => $echo]);
}
}
You also need to edit the myapp/templates/part.content.php
<p>Hello World <?php p($_['user']) ?></p>
<p><button id="hello">click me</button></p>
<p><textarea id="echo-content">
Send this as ajax
</textarea></p>
<p><button id="echo">Send ajax request</button></p>
Ajax response: <div id="echo-result"></div>
<p>listedudossier:<p> <?php
foreach ($_['listedudossier'] as $file) {
echo "type:".$file->getType();
echo "<p>";
echo "nom:".$file->getName();
echo "<p>";
echo "modif:".$file->getMTime();
echo "<p>";
}
From here you can test the code in a development environment:
https://github.com/owncloud/ocdev/blob/master/README.rst#installation

Getting an extension of an uploaded file in yii2

I'm trying to get an extension of a file during upload but I get an error that path info requires a string:
I have tried:
$path_parts = pathinfo($_FILES['evidence']['name']);
echo $path_parts['extension'];
How can extract file extension, for example jpeg, doc, pdf, etc.
If you are using yii2 kartik file input you can get the instance of yii\web\uploadedFile this way to:
$file = UploadedFile::getInstanceByName('evidence'); // Get File Object byName
// Then you can get extension by this:
$file->getExtension()
If you want to validate file as well then you can use FileValidator using adhoc role:
$validator = new FileValidator(['extensions' => ['png','jpg']]);
if( $validator->validate($file, $errors) ) {
// Validation success now you can save file using $file->saveAs method
} else {
// ToDO with error: print_r($errors);
}
It's better not use $_FILES directly in Yii2 since framework provides abstraction with a class yii\web\UploadedFile. There is also separate page in guide describing working with uploaded files.
There is an example with model.
namespace app\models;
use yii\base\Model;
use yii\web\UploadedFile;
class UploadForm extends Model
{
/**
* #var UploadedFile
*/
public $imageFile;
public function rules()
{
return [
[['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'],
];
}
public function upload()
{
if ($this->validate()) {
$this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension);
return true;
} else {
return false;
}
}
}
As you can see, extension is extracted using extension property ($this->imageFile->extension).
There are more info about form settings, handling in controller, uploading multiple files. All this can be found by the link mentioned above.

show custom exceptions in kohana 3.3 template controller

I wish to show custom exception pages in my application. In documentation is written:
"Note: We can also use HMVC to issue a sub-request to another page rather than generating the Response in the HTTP_Exception itself." The problem is i don't know how to do it.
Code which I am using is:
class HTTP_Exception_404 extends Kohana_HTTP_Exception_404 {
/**
* Generate a Response for the 404 Exception.
*
* The user should be shown a nice 404 page.
*
* #return Response
*/
public function get_response()
{
$view = View::factory('errors/404');
// Remembering that `$this` is an instance of HTTP_Exception_404
$view->message = $this->getMessage();
$response = Response::factory()
->status(404)
->body($view->render());
return $response;
}
}
It is working but i need to show the message in currently used template.
Regards
You shouldn't need to bother with sub-requests if all you want is to use the template. Here's the easy way:
class HTTP_Exception_404 extends Kohana_HTTP_Exception_404 {
public function get_response()
{
$view = View::factory('errors/404');
// Remembering that `$this` is an instance of HTTP_Exception_404
$view->message = $this->getMessage();
// Wrap it in our layout
$layout = $layout = View::factory('template');
$layout->body = $view->render();
$response = Response::factory()
->status(404)
->body($layout->render());
return $response;
}
}
If you really must use a sub-request, just do the sub-request normally:
class HTTP_Exception_404 extends Kohana_HTTP_Exception_404 {
public function get_response()
{
$request = Request::factory('route_to_error_page_action')
->method(Request::POST)
->post(array('message' => $this->getMessage()));
$response = $request->execute();
// If you didn't return the 404 code from the sub-request, uncomment this:
$response->status(404);
return $response;
}
}

Joomla3 custom server side form validation rule

I am new to joomla component development(J3 , MVC) and i am trying to create a custom server side form validation rule.
I added validate="machinename" to my forms field and created a the file models\rules\machinename.php
defined('_JEXEC') or die('Restricted access');
jimport('joomla.form.formrule');
class JFormRuleMachinename extends JFormRule
{
protected $regex = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/';
}
I have a empty controller in controllers\field.php
defined('_JEXEC') or die('Restricted access');
// import Joomla controllerform library
jimport('joomla.application.component.controllerform');
class SampleControllerField extends JControllerForm
{
}
and the model is in models\field.php
defined('_JEXEC') or die('Restricted access');
// import Joomla modelform library
jimport('joomla.application.component.modeladmin');
/**
* HelloWorld Model
*/
class SampleModelField extends JModelAdmin
{
public function getTable($type = 'Field', $prefix = 'SampleTable', $config = array())
{
return JTable::getInstance($type, $prefix, $config);
}
/**
* Method to get the record form.
*
* #param array $data Data for the form.
* #param boolean $loadData True if the form is to load its own data (default case), false if not.
* #return mixed A JForm object on success, false on failure
* #since 2.5
*/
public function getForm($data = array(), $loadData = true)
{
// Get the form.
$form = $this->loadForm('com_sample.field', 'field',
array('control' => 'jform', 'load_data' => $loadData));
if (empty($form))
{
return false;
}
return $form;
}
/**
* Method to get the data that should be injected in the form.
*
* #return mixed The data for the form.
* #since 2.5
*/
protected function loadFormData()
{
// Check the session for previously entered form data.
$data = JFactory::getApplication()->getUserState('com_sample.edit.field.data', array());
if (empty($data))
{
$data = $this->getItem();
}
return $data;
}
}
my components name is com_sample and everything was working fine (new,edit,delete) but then i added the validation rule to the form's field and now i am getting a error when submitting the form :
JForm::validateField() rule `machinename` missing.
my best guess is that i have a mistake in naming or the file location but i am not sure and can't find anything with googleing .
so help me pliz ...
Find the solution myself, it seems that you need to add the rules folder pathto the form definition so :
<form addrulepath="/administrator/components/com_sample/models/rules">
this solved my problem .
I was struggling with this problem. I read the error as meaning that Joomla couldn't find the rule file, but when I single-stepped through the core I realised that after loading the rule file, Jommla checks that an appropriately named class is within the rule. I'd introduced a typo to the class name. So my advice to anyone struggling with server-side validation is to check the rule file is where you'd expect, AND that the class name is correct. Obvious I know, but it took me ages to figure.

Add caching to Zend\Form\Annotation\AnnotationBuilder

As I've finally found a binary of memcache for PHP 5.4.4 on Windows, I'm speeding up the application I'm currently developing.
I've succeeded setting memcache as Doctrine ORM Mapping Cache driver, but I need to fix another leakage: Forms built using annotations.
I'm creating forms according to the Annotations section of the docs. Unfortunately, this takes a lot of time, especially when creating multiple forms for a single page.
Is it possible to add caching to this process? I've browsed through the code but it seems like the Zend\Form\Annotation\AnnotationBuilder always creates the form by reflecting the code and parsing the annotations. Thanks in advance.
You might wanna try something like this:
class ZendFormCachedController extends Zend_Controller_Action
{
protected $_formId = 'form';
public function indexAction()
{
$frontend = array(
'lifetime' => 7200,
'automatic_serialization' => true);
$backend = array('cache_dir' => '/tmp/');
$cache = Zend_Cache::factory('Core', 'File', $frontend, $backend);
if ($this->getRequest()->isPost()) {
$form = $this->getForm(new Zend_Form);
} else if (! $form = $cache->load($this->_formId)) {
$form = $this->getForm(new Zend_Form);
$cache->save($form->__toString(), $this->_formId);
}
$this->getHelper('layout')->setLayout('zend-form');
$this->view->form = $form;
}
Found here.
Louis's answer didn't work for me so what I did was simply extend AnnotationBuilder's constructor to take a cache object and then modified getFormSpecification to use that cache to cache the result. My function is below..
Very quick work around...sure it could be improved. In my case, I was limited to some old hardware and this took the load time on a page from 10+ seconds to about 1 second
/**
* Creates and returns a form specification for use with a factory
*
* Parses the object provided, and processes annotations for the class and
* all properties. Information from annotations is then used to create
* specifications for a form, its elements, and its input filter.
*
* MODIFIED: Now uses local cache to store parsed annotations
*
* #param string|object $entity Either an instance or a valid class name for an entity
* #throws Exception\InvalidArgumentException if $entity is not an object or class name
* #return ArrayObject
*/
public function getFormSpecification($entity)
{
if (!is_object($entity)) {
if ((is_string($entity) && (!class_exists($entity))) // non-existent class
|| (!is_string($entity)) // not an object or string
) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects an object or valid class name; received "%s"',
__METHOD__,
var_export($entity, 1)
));
}
}
$formSpec = NULL;
if ($this->cache) {
//generate cache key from entity name
$cacheKey = (is_string($entity) ? $entity : get_class($entity)) . '_form_cache';
//get the cached form annotations, try cache first
$formSpec = $this->cache->getItem($cacheKey);
}
if (empty($formSpec)) {
$this->entity = $entity;
$annotationManager = $this->getAnnotationManager();
$formSpec = new ArrayObject();
$filterSpec = new ArrayObject();
$reflection = new ClassReflection($entity);
$annotations = $reflection->getAnnotations($annotationManager);
if ($annotations instanceof AnnotationCollection) {
$this->configureForm($annotations, $reflection, $formSpec, $filterSpec);
}
foreach ($reflection->getProperties() as $property) {
$annotations = $property->getAnnotations($annotationManager);
if ($annotations instanceof AnnotationCollection) {
$this->configureElement($annotations, $property, $formSpec, $filterSpec);
}
}
if (!isset($formSpec['input_filter'])) {
$formSpec['input_filter'] = $filterSpec;
}
//save annotations to cache
if ($this->cache) {
$this->cache->addItem($cacheKey, $formSpec);
}
}
return $formSpec;
}

Categories