CakePHP SeminarskiRad(2)

Seminarski rad na praksi

Tema: CakePHP, MVC (Model – View – Controller) Juli, 2007

Mentor: Mustafa Hodžić

Student: Jasmina Šero

SADRŽAJ
Seminarski rad na praksi......................................................................................................1 SADRŽAJ............................................................................................................................2 1. Uvod.................................................................................................................................3 2. MVC (Models – Views – Controllers).............................................................................4 2.1. Model........................................................................................................................5 2.1.1. Definiranje i upit sa hasOne: .............................................................................7 2.1.2. Definiranje i upit sa belongsTo..........................................................................8 2.1.3. Definiranje i upit sa hasMany............................................................................9 2.1.4. Definiranje i upit sa hasAndBelongsToMany (HABTM)...............................11 2.2. View .......................................................................................................................20 2.3. Controller................................................................................................................23 2.3.2. Controller callback...........................................................................................27 Možemo kreirati callback u našem aplikacijskom Model-u ili Controller file-u. U ovom primjeru šaljemo email iz Model-a....................................................................................27 3. Helper.............................................................................................................................28 3.1. HTML Helper........................................................................................................28 3.2. TimeHelper............................................................................................................31 3.3. NumberHelper.........................................................................................................32 4. Scaffolding.....................................................................................................................34 5. Data Validation .............................................................................................................35 6. Components...................................................................................................................37 7. Servis “Anketa”.............................................................................................................39 7.1. Tabele servisa..........................................................................................................39 7.2. MVC servisa...........................................................................................................41 7.2.1. Model ..............................................................................................................41 7.2.2. View.................................................................................................................42 7.2.3. Controller.........................................................................................................46 7. 3. Upustvo za servis..................................................................................................52 ZAKLJUČAK....................................................................................................................59 LITERATURA..................................................................................................................60

2

1. Uvod
CakePHP je open source web aplikacijski framework napisan za PHP, distribuiran od MIT license. Razvoj Cake-a počinje u 2005-oj godini od tada je nastalo nekoliko projekata i još uvijek se radi na razvoju novih. CakePHP je dobro dokumentovan framework, inspiriran konceptom predstavljenim u RoR-u (Ruby on Rails), koji može biti primjenjen u PHP4 – PHP5. Cake olakšava komunikaciju user interface-a sa bazom podataka, brz je, koristi MVC ( Model – View – Controller ) arhitekturu, te time forsira objektno i uredno programiranje. Ima podršku za cashe, ajax, RSS Helper, a uskoro bi trebao dobiti još “masu” stvari. Kod testiranja i debagiranja aplikacije bilo koji developer koji primjenjuje CakePHP strukturu će biti sposoban da locira i ispravi grešku bez znanja svih detalja o kodu. Neke osobine CakePHP- a: Kompatibilan je sa PHP4 i PHP5 Fleksibalan View Cashing Application Scaffolding Access Control List View Helpers za AJAX, Javascript, HTML forme,... Radi iz bilo kojeg website poddirektorija Brz, fleksibilan template (PHP sintaksa sa Helper metodama) Ugrađena validacija Website directory nezavisan View caching

Da bi koristili CakePHP moramo imati HTTP sever (wamp, apache), PHP 4 ili 5, database engine (MaSQL, PostgreSQL). Instalacija Cake-a: downloadujemo jednu od verzija CakePHP-a (www.cakephp.org, www.forgephp.org ). Otpakujemo folder i snimimo na web server u webroot direktorij ( wamp, apache,…npr. C:\wamp\www), zatim učitamo stranicu kao localhost u web browser-u. Indexnu stranu podešavamo u /app/config/routes.php file-u, detalje o podešavanju i instalaciji CakePHP-a možemo naći u CakePHP Manual dokumentu, (www.cakephp.org ).

3

MVC često vidimo u web aplikacijama. koja postoji kao veza između tri objekta: Model. u kompleksnim kompjuterskim aplikacijama koje predstavljaju dugi niz korisničkih podataka. Svi naši entiteti (Models). koji zatim odabire View za prikaz rezultata tj. gdje je view ustvari HTML stranica. Developeri često žele odvojiti podatke (model) od korisničkog interfejsa (view). MVC rješava ovaj problem odvajanjem pristupa podacima od biznis logike iz prezentacijskih podataka i korisničkog interfejsa. MVC (Models – Views – Controllers) MVC je arhitekturalni patern koji se koristi u softverskom inžinjeringu. tako da nećemo mijenjati Contoller ako želimo promijeniti izgled nekog input polja. Controller prima svaki HTTP zahtijev. funkcije ovih entiteta (Controllers). korisnički interfejs (Views) smješteni su u odvojenim fajlovima. MVC je software dizajn patern koji pomaže u logičkom odvajanju koda.… Controller i View zavise od Model-a. Model je predstavljen stvarnim sadržajem. zato što zahtijevaju podatke iz Model-a. koji je obićno smješten u bazu podataka ili XML fajl. a View generira HTTP odgovor. tako da mijenjaju korisnički interfejs koji ne daje podatke i podaci mogu biti reorganizovani bez mijenjanja interfejsa. bilo koji input dolazi preko Controllera. View i Controller. to je kao konceptualna paradigma.2. a controller je kod koji skuplja dinamičke podatke i generira sadržaj sa HTML-om. kao što možemo vidjeti na sljedećoj slici Input -> Processing -> Output Controller -> Model -> View 4 . uključujući komponentu controller.

1. prima ulaze (zahtijeve) i prevodi ih za Model i View Modeli. aplikacijski flow i business logic View ekstraktuje podatke iz Modela i formatira ih za prezentaciju Controller pristupa aplikacijskom flow. Model 5 . kao što se može vidjeti u sljedećem primjeru: • app/ o o o o o o o o config/ controllers/ models/ plugins/ tmp/ vendors/ views/ webroot/ • cake/ config/ docs/ libs/ vendors/ o o o • 2. views (pogledi) i kontroleri nalaze se u predefinisanom direktoriju sa CakePHP– ovom direktori strukturom.- Model enkapsulira aplikacijeske podatke.

Naravno.Foreign (strani) ključ: [ime modela u jednini]_id. s tim da je ime tabele u množini. npr. imena tabela trebaju biti “posts” i “authors”. starni ključ u “authors” tabeli bi trabao biti “post_id”. ako imamo tabelu “answers”.hasOne . npr. ime modela za tabelu “posts” je “Post”. ako želimo snimiti informacije o blog postu i autorima. jer scaffolding pronalazi i koristi asocijacije između modela.Rich Domain Model uključuje kompleksne web objekte i mnoge dizajn paterne Koji model ćemo koristiti zavisi od konteksta aplikacije. Za korektno korištenje asocijacije najbolje je slijediti CakePHP konvenciju imenovanja.php <?php class Answer extends AppModel { var $name = 'Answer'.Model je objekt MVC arhitekture. Omogućava pristup bazi podataka i tabelama u bazi. Jedna od najmočniji osobina CakePHP je veza između modela. } ?> Domain Model je layer objekt koji apstraktnu logiku. podatke i probleme aplikacije odvaja. ime modela i ime tabele: . . CakePHP konvencija imenovanja odnosi se na foreign (strani) ključ. trebamo imati i model “Answer”. ponaša se kao primarni drajver u aplikaciji. strani ključ u “authors” tabeli za povezivanje sa Post modelom koristi asocijaciju Authors belongs to. možemo koristiti scaffolding da predstavimo sebi aplikacijske podatke. Cake automatski povezuje modele. 6 . Može se klasificirati u dvije kategorije: . odvaja prezentacijku logiku od aplikacijske. Ukoliko koristimo CakePHP konvenciju imenovanja.Ime modela: [ime tabele u jednini]. Po defaultnu imena modela su u jednini i imaju ime isto kao i tabela u bazi. Primjer: Answer model se treba snimiti u app/models/answer.hasAndBelongsToMany Kada je veza između modela definisana.Ime tabele: [ime objekta u množini].hasMany .Simple Domain Model ima one – to – one odgovornost između business objekata i tabela u bazi podataka . npr. Postoje četiri tipa veza: . možemo uvijek podesiti model asocijacije da rade bez CakePHP konvencije imenovanja.belongsTo . a ime modela za tabelu ”authors” je “Author”. nema nikakve prezentacijske osobine i nikakvu odgovornost prema HTTP zahtijevima. sadrži aplikacijske podatke i logiku. .tj.

editor_id. order: redoslije asocijacija modela. Scaffolding očekuje asocijacije u istom poretku kao što su ključevi u tabeli. publisher_id. } ?> Varijablu $hasOne Cake koristi za pravljenje asocijacije između User i Profile modela. Editor i Publisher. Primjer: Ako želimo kreirati jednostavni user menađment sistem za blog. Međutim user-i mogu dodavati i komentare pa imamo asocijaciju hasMany (User hasMany Comments). npr. svaki user ima jedinstven profil što omogućava asocijaciju hasOne (User hasOne Profile). 'order' => ''.header_color = ’green’ ”. 'foreignKey' => 'user_id' ) ).CakePHP scaffolding očekuje asocijacije u istom poretku kao i kolona. Trebamo i tri ključa: author_id. 'dependent' => true. tako da imamo asocijaciju hasAndBelongToMany (Post hasAndBelongToMany Tags). var $hasOne = array('Profile' => array('className' => 'Profile'. tako ako imamo Article koji belongsTo (pripada) modelima: Author. prva Author. da definišemo ovakav uslov treba specifirati SQL uslov: “Profile. ako želimo veze modela u specifičnom redoslijedu. npr. 2.1.1. postavimo vrijednost ključa koristeći SQL redoslijed: “Pofile. Definiranje i upit sa hasOne: <?php class User extends AppModel { var $name = 'User'. druga Editor i posljednja Publisher. ako želimo specifirati “Profile” ime klase modela uslovi: SQL fragmenti stanja (uslova) koji definišu taj odnos. Kada želimo da user sistem radi dozvolit ćemo Posts koji će biti povezan sa Tag objektima. Svaki ključ u nizu dozvoljava nam dalju konfiguraciju asocijacije: className: ime klase modela koji želimo povezati . 'conditions' => ''. Možemo koristiti ovo da kežemo Caku-u da samo poveže Profile koji ima green header.name ASC” 7 - .

var $belongsTo = array('User' => array('className' => 'User'. Definiranje i upit sa belongsTo Međutim User može zahtijevati da vidi Profil. 'conditions' => ''. pridruženi model je uništen kada je uništen predhodni forgeinKey: ime stranog ključa koji povezuje odgovarajući model $user = $this->User->read(null. trebali bi vidjeti asocijaciju User modela. primjer.- zavisnost: ukoliko je podešena na true. //output: Array ( [User] => Array ( [id] => 25 [first_name] => John [last_name] => Anderson [username] => psychic [password] => c4k3roxx ) Kada izvršimo find() ili findAll() poziva korišteni Profile model. putanja : /app/models/profile. 'foreignKey' => 'user_id' ) ). print_r($user). } ?> 8 . 'order' => ''.2. '25'). to ćemo omogućiti pomoću asocijacije belongsTo koju kreiramo u modelu Profil.php <?php class Profile extends AppModel { var $name = 'Profile'.1. kao što je: [Profile] => Array ( [id] => 4 [name] => Cool Blue [header_color] => aquamarine [user_id] = 25 ) ) 2.

3. 'order' => 'Comment. var $hasMany = array('Comment' => array('className' => 'Comment'. Kada izvršimo find() ili findAll() poziv Profil modela. 'finderQuery' => '' ) ). //output: Array ( [Profile] => Array ( [id] => 4 [name] => Cool Blue [header_color] => aquamarine [user_id] = 25 ) [User] => Array ( [id] => 25 [first_name] => John [last_name] => Anderson [username] => psychic [password] => c4k3roxx ) ) 2.created DESC'. Definiranje i upit sa hasMany Sada kada su User i Profile model povezani i rade.$belongsTo niz Cake koristi da poveže modele Profil i User. 'conditions' => 'Comment.moderated = 1'. 9 . 'foreignKey' => 'user_id'. treba napraviti sistem da User arhivu poveže sa Comment arhivom. print_r($profile).1. '4'). U User model trebamo dodati: <?php class User extends AppModel { var $name = 'User'. trebali bi vidjeti pridruženi User model kao što je: $profile = $this->Profile->read(null. 'dependent' => true. 'limit' => '5'. 'exclusive' => false.

“Comment. //output: Array ( [User] => Array ( [id] => 25 [first_name] => John [last_name] => Anderson [username] => psychic [password] => c4k3roxx ) [Profile] => Array ( [id] => 4 [name] => Cool Blue [header_color] => aquamarine [user_id] = 25 ) 10 .uslovi: SQL fragmenti stanja (uslova) koji definišu taj odnos.foreignKey: ime stranog ključa koji povezuje pridruženi model .limit (ograničenje).className: ime modela koji želimo povezati . “Comment. ako želimo povezati modele u određeni redoslijed. koji želimo da nam Cake dohvati.Cake koristi $hasMany niz da napravi vezu User i Comment modela.exclusive: ako je posatvljeno na true.finderQuery: specifikacija kompletne SQL izjave za dohvat veze. ovo je dobar način za kompleksne asocijacije koje ovise o više tabela Kada izvršimo find() ili findAll() poziv User modela.created DESC” . Svaki ključ u nizu dozvoljava dalju konfiguraciju veze: . npr. Možemo koristiti uslov da kažemo Cake-u da poveže samo Comment koji je ograničen.modereted = 1” . svi povezani objekti će biti obrisani u jednoj SQL izjavi bez da se beforeDelete callback pokrene . trebali bi vidjeti povezane Comment modele. kao što je: $user = $this->User->read(null. u ovom primjeru nismo htjeli sve komentare. '25').maksimalan broj veza modela. već samo 5 . npr. postavimo vrijednost ključa da koristi SQL redoslijed.redoslijed: redoslijed povezani modela. print_r($user).

1. Razlika između hasMany I hasAndBelongsToMany je ta što sa hasMany.koristan je kad imamo dva modela koja su povezana zajedno sa pridruženim tabelama. asocijacija 11 . Pridružene tabele sadrže individualne redove koji su povezani jedan sa drugim. ) ) ) Dobra ideja je definirati “Comment belongsTo User” asocijaciju. ) [2] => Array ( [id] => 269 [user_id] => 25 [body] => The hasMany assocation is really. ) [1] => Array ( [id] => 256 [user_id] => 25 [body] => The hasMany assocation is really nice to have. 2. Definiranje i upit sa hasAndBelongsToMany (HABTM) HasAndBelongsToMany je najteži za “pravljenje”.4. ) [3] => Array ( [id] => 285 [user_id] => 25 [body] => The hasMany assocation is extremely nice to have. ali je takođe i najviše korišten. tako da oba modela mogu vidjeti jedan drugi. ) [4] => Array ( [id] => 286 [user_id] => 25 [body] => The hasMany assocation is super nice to have.[Comment] => Array ( [0] => Array ( [id] => 247 [user_id] => 25 [body] => The hasMany assocation is nice to have. really nice to have. Ne definiranje asocijacija iz oba modela je često uobičajna greška kada pokušamo koristiti scaffolding.

jednostavni modeli i pridružena im imena tabela: . samo je user povezan sa komentarima. -. `status` tinyint(1) NOT NULL default '0'. `tag` varchar(100) default NULL. `user_id` int(10) default NULL. `tag_id` int(10) unsigned NOT NULL default '0'. `modified` datetime default NULL. PRIMARY KEY (`post_id`. sa HABTM. PRIMARY KEY (`id`) ) TYPE=MyISAM.Table structure for table `tags` -CREATE TABLE `tags` ( `id` int(10) unsigned NOT NULL auto_increment. PRIMARY KEY (`id`) ) TYPE=MyISAM. 12 .Table structure for table `posts` -- CREATE TABLE `posts` ( `id` int(10) unsigned NOT NULL auto_increment. HABTM pridružene tabele. -. Ako imamo User hasMany Comments.`tag_id`) ) TYPE=MyISAM. asocijacije modela su dijeljene.Posts i Tags: posts_tag .---------------------------------------------------------.---------------------------------------------------------. Primjer Posts HABTM Tags: --.Table structure for table `posts_tags` -CREATE TABLE `posts_tags` ( `post_id` int(10) unsigned NOT NULL default '0'.Categories i Articles: articles_categories HABTM da poveže tabele treba najmanje dva strana ključa modela koji su povezani. Za naš primjer to su “post_id” i “tag_id”. `title` varchar(50) default NULL.modela nije dijeljena (shared). `created` datetime default NULL.Monkeys i IceCubes: ice_cubes_monkeys . `body` text.

} ?> Cake koristi $hasAndBelongsToMany niz da napravi asocijaciju između Post i Tag modela. 'associationForeignKey'=> 'tag_id'. //output: Array ( [Post] => Array ( [id] => 2 [user_id] => 25 [title] => Cake Model Associations [body] => Time saving. 'limit' => ''. and powerful. print_r($post). 'deleteQuery' => ''. 'conditions' => ''. 'order' => ''. trebali bi vidjeti pridruženi Tag model kao što je: $post = $this->Post->read(null. '2'). [created] => 2006-04-15 09:33:24 [modified] => 2006-04-15 09:33:24 [status] => 1 ) [Tag] => Array ( [0] => Array ( [id] => 247 [tag] => CakePHP ) 13 . var $hasAndBelongsToMany = array('Tag' => array('className' => 'Tag'. 'foreignKey' => 'post_id'.php hasAndBelongsToMany <?php class Post extends AppModel { var $name = 'Post'. ) ). easy. 'joinTable' => 'posts_tags'. Kada izvršimo find() ili findAll() poziv Post modela. 'uniq' => true.Sa ovim tabelama definišemo asocijaciju u Post modelu: /app/models/post. 'finderQuery' => ''.

1. Primjer specifične metode u modelu je par metoda za hiding/unhiding post u blogu. '0'). $this->saveField('hidden'. treba je smjestiti u /app/app_model. } } function unhide ($id=null) { if ($id) { $this->id = $id. } 14 . AppModel klasa je orginalno definisana u cake/ direktoriju. function hide ($id=null) { if ($id) { $this->id = $id.1.php. Treba da sadrži metode koje su dijeljene između dva ili više modela. '1').php. $this->saveField('hidden'. kao što vidimo u sljedećem primjeru: <?php class Post extends AppModel { var $name = 'Post'. Time ona proširuje klasu Model koja je standardna Cake biblioteka definisana u cake/libs/model. ali ako želimo kreirati vlastitu. Model funkcije Modeli su klase proširene AppModel klasom.[1] => Array ( [id] => 256 [tag] => Powerful Software ) ) ) 2.

int $page. $order. $limit. $this->Property->findAllByState('AZ'). array $fields. Ove magične funkcije mogu biti korištene kao shortcut za pretragu tabela za redom koji je dat određenim poljem i određenom vrijednošću. Vraća specifično polje iz prvog rekorda koje odgovara $conditions. $order. primjer: $this->Post->findByTitle('My First Blog Post'). int $recursive. findAll( ) operacije pokušavaju vratiti odgovarajući model nađen sa findAll( ). $page. int $limit. $this->Author->findByLastName('Rogers'). string $conditions. find ($conditions $fields. //primjer:$conditions="race='wookie' AND Kada je $recursive opcija postavljena na vrijednost veću od 1.} } ?> U sljedećem primjeru vidimo nekoliko standardni načina za dobivanje naših podataka koristeći model: findAll ($conditions thermal_detonators > 3". Ako naš property ima više vlasnika koji su uključeni u više ugovora. Treba samo izabrati ime polja koji želimo tražiti. $fields. string array string int $conditions. $recursive). Povratni rezultat je niz formatiran koji je nađen sa find() ili findAll( ). $recursive. $recursive). $order. 15 . $fields. findAll( ) na našem Property modelu će vratiti te odgovarajuće modele. $this->Specimen->findAllByKingdom('Animalia'). string $order.

string $conditions. ako želimo generirati listu uloga baziranu na Role modelu. // But we also want the previous and next images. $limit.. 'id'. $value). string $conditions. Ovo radi samo za numerička i date polja. Npr. string string int string string $conditions. $this->Image->find("id = $id"). Ova funkcija je shortcut za dobivanje liste ključnih vrijednosti parova – pogotovo praktična za kreiranje html select tagova iz liste našeg modela. Ovo može biti korisno u situaciji kada želimo 'Previous' i 'Next' linkove koji vode korisnika kroz uređenu sekvencu u našem model-u ulaza. $this->Image->findNeighbours(null. $valuePath). $this->set('neighbours'. Primjer: class ImagesController extends AppController { function view($id) { // Say we want to show the image. array $field. string $value. $conditions. Vraća niz sa susjednim modelom (sa samo navedenim poljima). $valuePath. $order.. $keyPath.. puni poziv može izgledati kao u sljedećem primjeru: $this->set( 16 . specifiran sa $field i $value i filtriran sa SQL uslovima. Vraća broj rekorda koji odgovaraju zadanim uslovima. $keyPath. } } findCount ($conditions). $order. generateList ($conditions. $limit. $this->set('image'. $id).findNeighbours ($conditions $field..

'Roles', $this->Role->generateList(null, 'role_name ASC', null, '{n}.Role.id', '{n}.Role.role_name') ); //This would return something like: array( '1' => 'Account Manager', '2' => 'Account Viewer', '3' => 'System Manager', '4' => 'Site Visitor' ); read ( $fields, $id); string $fields; string $id;

Ovu funkciju možemo koristiti da dobijemo polja i vrijednosti polja iz trenutačno učitanog rekorda ili rekorda specifiranog sa $id.
query ($query); string $query; execute ($query); string $query;

SQL poziv možemo napraviti koristeći model query( ) i execute ( )metode. Razlika između query( ) i execute ( ) je ta što, query( ) koristimo da napravimo korisnički SQL upit, a execute( ) koristimo da napravimo korisničke SQL komande. Primjer:
<?php class Post extends AppModel { var $name = 'Post';

} ?>

function posterFirstName() { $ret = $this->query("SELECT first_name FROM posters_table WHERE poster_id = 1"); $firstName = $ret[0]['first_name']; return $firstName; }

Većina model pretraživača uključuju prosljeđivanje uslova u jednom ili u drugom pravcu. Najjednostavniji pristup ovome je pomoću WHERE klauzule iz SQL-a, ali ako nam je potrebno više kontrole možemo koristiti nizove. Korištenje nizova je jednosatvno i lako za čitanje i jednostavno je za pravljenje upita. Sintaksa odvaja elemente upita u diskretne, 17

manipulativne dijelove. Ovo dozvoljava Cake-u da generira najviše moguće efikasne upite, osigurava odgovarajuću SQL sintaksu. Upit baziran na nizu izgleda ovako:
$conditions = array("Post.title" => "This is a post"); //Example usage with a model: $this->Post->find($conditions);

Traži bilo koji post gdje naslov odgovara stringu “This is a post”, mogli smo koristiti samo “title” kao ime polja. Kada pravimo upite, dobra je praksa uvijek specificirati ime modela, kao poboljšanje jasnoće koda i kao pomoć pri prevenciji kolizija u budućnosti ako odlučimo mijenjati našu šemu. I sa drugim komparacijama je isto, npr. ako želimo naći sve postove gdje naslov nije “This is a post”.
array("Post.title" => "<> This is a post")

Sve što je dodano nalazi se između ‘< >’ tagova prije izraza. Cake može obraditi bilo koju validnu SQL komparaciju operatora uključujući odgovarajuće izraze koristeći LIKE, BETWEEN. Npr. Ako želimo naći post gdje je naslov dat kao set vrijednosti:
array("Post.title" => array("First post", "Second post", "Third post"))

Dodavanje dodatnih filtera uslovima je jednostavno kao dodavanje dodatnih parova vrijednosti /ključeva nizu.
array ( "Post.title" => array("First post", "Second post", "Third post"), "Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks")) )

Cake prihvata sve validne SQL bool-ove operacije, uključujući AND, OR, NOT, XOR,...i možemo ih pisati malim ili velikim slovima. Po defaultu, Cake pristupa višestrukim uslovima sa bool-ovim AND, što znači, odlomak koji želimo odgovara samo postu koji je kreiran prije dvije sedmice i naslov odgovara jednom u datom setu. Mi možemo jednostavno naći post koji odgovara bilo kojem uslovu:
array ("or" => array ( "Post.title" => array("First post", "Second post", "Third post"), "Post.created" => "> " . date('Y-m-d', strtotime("-2 weeks")) ) )

18

Snimanje podataka Da bi snimili podatke u naš model, trebamo dostaviti podatke koje želimo snimiti, podaci koji se šalju save( ) metodi trebaju biti u sljedećoj formi:
Array ( [ModelName] => Array ( [fieldname1] => 'value' [fieldname2] => 'value' ) )

U redoslijedu slanja podataka controller-u u ovom načinu, lakše je koristiti HTML helper za ovo, jer kreira forme elemenata koje su imenovane na način koji Cake očekuje. Samo trebamo biti sigurni da forme elemenata imaju imena kao što je data[Modelname] [fieldname], koristiti $html->input('Model/fieldname') je najjednostavnije. Podaci poslani iz forme se automatski formatiraju i budu smješteni u $this->data u contoller-u, tako da je snimanje podataka iz web formi brzo. Edit funkcija za svojstvo controller-a može izgledati ovako:
function edit($id) { // The property model is automatically loaded for us at $this->Property. // Check to see if we have form data... if (empty($this->data)) { $this->Property->id = $id; $this->data = $this->Property->read();//populate the form fields with the current row } else { // Here's where we try to save our data. Automagic validation checking if ($this->Property->save($this->data['Property'])) { //Flash a message and redirect. $this->flash('Your information has been saved.', '/properties/view/'.$this->data['Property']['id'], 2); } //if some fields are invalid or save fails the form will render } }

Delete funkcija: del ( $id, 19

$cascade). view za PostController::add() nalazi se u /app/views/posts/add. 2.') ?> </p> <p> <?php echo $html->submit('Save') ?> </p> </form> 20 . koji se obično imenuje poslije akcije. Ako je model povezan sa drugim modelima. string $id. array('rows'=>'10')) ?> <?php echo $html->tagErrorMsg('Post/body'. tj. View View je page template.') ?> </p> <p> Body: <?php echo $html->textarea('Post/body'. Ako je $cascade posatvljen na true.thtml. ova metoda će takođe obrisati povezani model. npr. Brisanje modela specificiramo sa $id ili sa aktualnim id-om modela. boolean $cascade.2. 'Body is required. nakon uspješnog brisanja. array('size' => '40'))?> <?php echo $html->tagErrorMsg('Post/title'. vraća true pri uspjehu. <h1>Add Post</h1> <form method="post" action="<?php echo $html->url('/posts/add')?>"> <p> Title: <?php echo $html->input('Post/title'. 'Title is required.

2. Popularni template Smarty je primjer template-a koji koristi custem syntax metodu. Layouts Layouts sadrži prezentacijski kod i bilo što što želimo vidjeti u view-u treba biti smješteno u layout-u. najviše view file-ova sadrži HTML. U Template počinje se sa skeletom outputa i insertuju domain podaci u to. Primjer kako može izgledati defaultni layout <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.w3..org/TR/xhtml1/DTD/xhtml1-transitional.Views omogućava sve aspekte prezentacije. View takođe može biti bilo koji set podataka. View ne treba sadržavati puno logike.</div> 21 . Jednom kada defaultni layout bude kreiran.org/1999/xhtml"> <head> <title><?php echo $title_for_layout?></title> <link rel="shortcut icon" href="favicon. Cake dolazi sa velikim brojem helper-a. include it here --> <div id="header"> <div id="menu">.dtd"> <html xmlns="http://www.If you'd like some sort of menu to show up on all of your views. ali nije dobro da View poziva metode Model-a kao što je update. eksktaktuje podatke iz Modela i priprema ih kao HTML za web stranicu ili kao XML za web server ili kao tekst za mail.thtml) koji su slobodni od kompleksnog PHP koda. View : . image.. Cake views su prilično jednostavni PHP file-ovi. Transform View ekstraktuje podatke iz modela i transformiše ih u traženi output format.thtml. dok u Transform View-u počinje se sa podacima i gradi se output prema tome. linkove. Kada kreiramo layout treba “reči” Cake gdje se nalazi controller.w3. HTML Helper je po defaultu dostupan u svakom view-u i to je najkorišteniji helper u view-u. Layout file-ovi nalaze se u /app/views/layouts. npr.layouts . Razlika između Template View-a i Transform View-a je u pristupu data flow-u. View ima pristup Model-u. zato nema puno korištenih javnih funkcija u view-u.. Koristan je kod kreiranja formi.1. controller view kod biva smješten unutar defaultnog layout-a. a može se kreirati i vlastiti.ico" type="image/x-icon"> </head> <body> <!-.. XML. to bi trebalo uraditi preko Controller-a. Template View je primarni patern koji koristi template file (obično HTML) koji uključuje specijalne markere koji su zamijenjeni sa podacima iz modela kada je Template View izvršen.elements 2.0 Transitional//EN" "http://www. mediu…Mnoge funkcije u view-u su dostupne preko helpera. uključuje scripte. Postoje dva dizajn paterna koja se koriste u view-u: Template View i Transform View. Cake-ov defaultni layout može biti zamijenjen novim defaultnim layout-om kao /app/views/layouts/defaults.Svi pogledi (views) unutar Cake-a se nalaze u odvojenim fajlovima (*.

.. Primjer: Poziv elemenata bez parametra Poziv elementa kao niza podataka: <?php echo $this->renderElement('helpbox'. Element je bazično mini view koji koji može biti uključen u drugi View.Add a footer to each displayed page --> <div id="footer">. this text is very helpful. Cake pomaže da ponavljamo dio website-a koji nam je potreban.2. Help boksovi. elementi se mogu koristiti da View učine više čitljivim. 2.</div> <!-. navigacijka kontrola.</div> </body> </html> Možemo kreirati koliko hoćemo layout-a u Cake-u samo ih treba smjestiti u /app/views/layouts direktorij.ekstra meniji su implementirani u Cake-u kao elementi. ponekad u različitim mjestima u layout-u. Elements Mnoge aplikacije imaju male blokove prezentacijskog koda koji se treba ponavljati na više stranica. 22 . array("helptext" => "Oh.Here's where I want my views to be displayed --> <?php echo $content_for_layout ?> <!-. da bi dozvolili pristup podacima šaljemo ih kao imenovani parameter u niz. ?> <?php echo $this->renderElement('helpbox').")).2. Elementi po defaultu nemaju nikakav pristup podacima. Elementi se nalaze u /app/views/elements/ folderu I imaju .thtml file ekstenziju. ?> U Element file-u sve propuštene varijable su varijable imenovane kao i niz .

1.Application Controller Front Controller često pomaže u centralizaciji aplikacijkog flow-a u jednostavnim tačkama.2.3. Command patern enkapsulira akciju u objekte. U sadržaju web aplikacije koristan je cilj koda koji odvaja koncentrirani Command da brine o radu pojedinačni HTTP zahtijeva. Ime controller file-a piše se malim slovima i završava sa '_controller'.php. PeopleController. klase AppController-a definisane su u app/app_controller. Front Controller je odličan za centralizaciju. ime akcije controller-a treba početi sa '-'.Front Controller . Controller Controller procesira i odgovara na događaje. pa ako imamo controller klasu koja se zove PostsController. Da bi zaštitili vidljivost. Tipičan način impelmentacije Controller-a je koristeći Command paterne.php. Application Controller je centar onog čime MVC Controller operira.php. primjeri: people_controller.3. Manipulacija sa nekoliko specijalnih varijabli u controller-u dozvoljava da koristimo neke prednosti extra Cake funkcionalnosti. Primarna odgovornost je da odluči šta aplikacija treba odgovoriti na traženi zahtijev. za privatnu vidljivost. UsersController. Controller varijable: 23 . Ime controller klase zavrašava sa „Controller“ npr. Controller se koristi za logičko upravljanje modelom. file controller će biti posts_controller.ime treba početi sa '. tako da možemo parametrizirati upit. Imamo dvije vrste Controller-a: . tipična korisnička akcija i poziva promjene na modelu. 2. line_items_controller. really_nifty_things_controller.php.php i treba da sadrže metode koje su dijeljenene imeđu dva ili više Contoller-a. ime Controllera u Cake-u je uvijek u množini.-'. Centralizacija može pomoći da razumijemo kako kompleksan sistem operira i navodi jednostavna mjesta gdje možemo ubaciti globalni kod.

.3. treba pokušati dodati sljedeću liniju koda u controller: var $uses = array('Fraggle'. ali ako želimo pristupiti $this->Smurf.- - - $uses – ako controller koristi više od jednog modela. dobit ćemo poruku o greški u view-u. 24 . Primjer: class ProductsController extends AppController { var $beforeFilter = array('checkAccess').'Smurf'). Najčešće se koristi da se dobiju informacije (podaci) koje su date controller-u preko POST ili GET operatora.1. dostupan je po defaultu ali ako definišemo $helper bez HtmlHelpera. $autoRender .. Treba uključiti HtmlHelper u $helpers ako ga želimo koristiti.'Javascript'). Ova funkcionalnost je veoma dobra za access control. $this->data Primjer: slanje podataka POST metodom iz HTML Helper-a u controller // A HTML Helper is used to create a form element $html->input('User/first_name').Controller parametri Controller parametri su dostupni preko $this->params u Cake controller-u. ova varijabla se koristi da dajemo podatke u controller i omogućava pristup informacijama o tačnom upitu. function checkAccess() { //Logic to check user identity and access would go here. var $components = array('acl'). FragglesController će automatski učitati $this->Fraggle. var $helpers = array('Html'.'Ajax'. } } 2. ova varijabla koristi se za učitavanje komponenata koje su nam potrebne npr. HTML helper se automatski učitava.postavljajući ovo na false će zaustaviti akciju od automatskog izvođenja $components – kao $helpers i $uses.. ali može se ova varijabla koristiti i za druge helpere npr. iako je automatski prije bio dostupan. } function index() { //When this action is called. Treba primjetiti da smo uključili Fraggle model u $uses niz. $beforeFilter – koristimo ovu varijablu ako želimo da se kod pokreće svaki put kada se akcija pozove . $helper – ova varijabla se koristi za učitavanje helpera u view. checkAccess() is called first.možemo vidjeti korisničke permisije prije nego se izvrši bilo koja akcija.

$value). controller napravi varijablu $color dostupnom u view-u. 25 .. mixed $value.// When rendered in the HTML would look something like: <input name="data[User][first_name]" value="" type="text" /> // And when submitted to the controller via POST. . Funkcija index nam prikazuje početnu stranicu tj. koja prikazuje naslov postojeće vijesti. . $this->flash ('Your post has been saved.' has been deleted. nizovi.. '/posts'). cijele nizove. Omogućava dodavanje novih vijesti. Save() metoda provjerava greške i neće snimiti podatke ako postoji greška.render ( ) je funkcija koja se često ne koristi.. edit i delete vijesti koje su nalaze u bazi. parametri funkcije mogu biti jednostavne vrijednosti. add ( ).$this->flash( ) funkcija zove se upravljačka funkcija koja prikazuje korisniku poruku npr.. tj.set( ) funkcija je glavni put da uzmemo podatke iz controller-a da bi ih vidjeli.'/posts'). U sljedećem primjeru imamo Post controller sa četiri funkcije: funkcija index ( ).vraća broj grešaka koje su generirane pri neuspješnom snimanju podataka. . ako napišemo set('color'. pokreće se automatski po zahtijevu. Ispisuje poruku korisniku da je njegov post snimljen ili $this->flash ('The post with id: '. // shows up in $this->data['User']['first_name'] Array ( [data] => Array ( [User] => Array ( [username] => mrrogers [password] => myn3ighb0r [first_name] => Mister [last_name] => Rogers ) ) ) Akcija je jedinstvena funkcionalnost kontrolera. .Jednom kada koristimo set(). početna.$id.validateErrors( ) .'.'. varijablama može se pristupiti u view-u. edit ( ) i delete ( ). stranicu koju smo postavili da bude indexna. set($var. string $var.'blue') . ispisuje poruku da je post izbrisan. Funkciju set( ) možemo koristiti da uzmemo bilo šta: jednosatvne vrijednosti. jer render se automatski poziva na kraju svake kontrolne akcije.

} { 26 . function index() { } { } { function add() if (!empty($this->data)) { if ($this->Post->save($this->data)) { $this->flash('Your post has been saved. $this->data = $this->Post->read(). $this->flash('The post with id: '.' has been deleted. $this->Post->findAll()). } function edit($id = null) { if (empty($this->data)) { $this->Post->id = $id. } else { if ($this->Post->save($this->data['Post'])) { $this->flash('Your post has been updated.$id. $this->set('posts'.'.'.'.'/posts').<?php class PostsController extends AppController { var $name = 'Posts'.'/posts'). } } function delete($id) $this->Post->del($id). $this->set('post'. } } } } ?> function view($id = null) $this->Post->id = $id. '/posts'). $this->Post->read()).

array $options. Treba deklarisati funkcije u controller-u koristeći parametre i povratne vrijednosti.2. poziva se nakon svake controller akcije .beforeFilter ( ). prikazuje view.3. Controller callback U Cake-u callback je način izvršavanja nekog koda prije ili poslije Model ili Contoller metoda.2. string $url. Uzimanje podataka iz controller-a je jednostavno. .requestAction ( $url. // Ovdje je jednostavni controller class UsersController extends AppController { function getUserList() { $this->User->findAll(). ako $extra niz uključuje 'return' . poziva se nakon controller logike i prije nego što je view prikazan Sljedeće funkcije su dio Cake objekt klase. U ovom primjeru šaljemo email iz Model-a <?php // File: /app/models/bookmark. AutoRender automatski postavlja true za controller akciju. ali one mogu biti dostupne i u controller-u . poziva se prije svake controller akcije.beforeRender ( ). Možemo koristiti requestAction da dobijemo podatke iz druge controller akcije ili da dobijemo potpuni render view iz controller-a.afterFilter ( ). } } Možemo kreirati callback u našem aplikacijskom Model-u ili Controller file-u. $url je Cake URL (/controllername/actionname/params). $options).php class Bookmark extends AppModel { 27 . koristi se samo upitna akcija u view-u u iz kojeg trebamo podatke. Ova funkcija poziva controller akciju iz bilo koje lokacije i vraća tj. Cake controller omogućava odgovarajući broj callback-ova koji možemo iskoristiti da bi ubacili logiku prije ili poslije značajnih funkcija controller-a. korisna za provjeru aktivni sesija i provjeru uloga .

php i snimimo je u /cake/config/ folder.ini. a to su: .. HTML Helper HTML Helper je jedan od načina Cake-a za brži i manje monoton razvoj. function afterSave() { // mail me when a new bookmark is added mail('example@example.var $name = 'Bookmark'. return true.1.com'. Postoje funkcije u ovom helperu koje insertuju mediu. liste. Omogućiti ubacivanje često ponovljene sekcije HTML koda 2. .Number . 28 .]]) U svakoj funkciji treba provjeriti je li prvi argument niz..ini. Omogućiti brzo i jednostavno kreiranje web formi Mnoge funkcije u HTML Helper-u koriste HTML tagove definisane u file-u tags. to provjeravamo sa funkcijom is_array ( ). Ako želimo koristiti Cake za ubacivanje dobro formirani i često korištenih elemenata u HTML kodu.php. 3. Ako želimo neke promjene napravimo kopiju /cake/config/tags. mixed $secondArg = null[.Text . } } ?> 3.HTML . HTML Helper funkcije takođe uključuju $htmlAttributes parametre. 'Bookmark saved to database').AJAX . treba provjeriti specifični ključ sa funkcijom array_key_exists ( ).Time . tabele.Cache Svaka metoda u helperu treba da prihvati argumente u sljedeći način: helperMethod(mixed $params[. HTML Helper ima dva cilja: 1. Helper Cake ima nekoliko već kreiranih helpera koje možemo iskoristiti. $return ).… Generiranje charset MET-a taga: charset ( $charset.Javacript . HTML Helper je veoma dobar u tome. ako je prvi argument niz.

org" >CakePHP Home</a> koji vidimo kao CakePHP Home Ako napišemo: <?php echo $html->tableHeaders(array('First Name'. $return = ‘false’). $rel=’stylesheet’.).string $charset.'Email'). Kreiranje linka u CSS stylesheet-u : css( $path.cakephp.'Last Name'. //$rel parameter dozvoljava kreiranje HTML Helperi Ako napišemo: <?php echo $html->css('donutczar'). boolean $return. ?> Cake nam vraća: <tr><th>First Name</th> <th>Last Name</th> <th>Email</th></tr> što prikazuje kao First Name Ako napišemo: Last Name Email <?php echo $html->tableCells(array('Dunnottar'. rel=vrijednost za tag $htmlAttributes. ?> Cake nam vraća: <a href="http://www.'http://www. $return = ‘false’.'email')).cakephp. string string array boolean $path. $htmlAttributes.css" /> koji učitava stil za našu stranicu Ako napišemo: <?php echo $html->link('CakePHP Home'. ?> CakePHP nam vraća: <link rel="stylesheet" type="text/css" href="/cake1point2/css/donutczar.'Ceiteach'.org'). $rel=’stylesheet’. ?> CakePHP nam vraća: 29 .

icon. } if (isset($confirm)) { $options['confirm'] = $confirm. 30 . ?> CakePHP nam vraća: <img src="/cake1point2/img/cake.icon.array('alt'=>'CakePHP icon')). } else { $title = $params. $options = null. $confirm = null) { if (is_array($params)) { extract($params. '://') === false) { $href = $this->url($href). unset($confirm). } if (!isset($href)) { $href = $title. } if (!isset($options)) { $options = null. } if (!isset($title)) { $title = null. EXTR_OVERWRITE).png'.png" alt="CakePHP icon" /> što prikazuje Primjer definicije HtmlHelper::link ( ) <?php function link($params. } if (strpos($href. $href = null.<tr><td>Dunnottar</td> <td>Ceiteach</td> <td>email</td></tr> što prikazuje : Dunnottar Ako napišemo: Ceiteach email <?php echo $html->image('cake.

12.password .'. string array boolean $filedName.submit password ($fieldName. $htmlAttributes.'. array $htmlAttributes. } ?> Form tagovi koje HTML Helper može generirati: . $return = false). return $this->assign('link'.. submit ( $buttonCaption.. 'options' => $this->_parseOptions($options). $htmlAttributes. $return = false. 3. $return = false). $htmlAttributes. string $buttonCaption. $values). ?> 31 . ?> CakePHP vraća: Source chara..nice Ako napišemo: <?php echo $time->nice('07-11-2007 18:13:15'). boolean $return = false. TimeHelper ..'). 'tagValue' => $title ).trim Ako napišemo: <?php echo $time->trim('Source character string.2.} $values = array( 'href' => $href. .

'created').format Ako napišemo: <?php echo $number->format('98765.3.'.'USD').4321'.isToday Ako napišemo: <?php echo $time->isToday('07-11-2007 18:13:15'). ?> CakePHP vraća: $98.currency Ako napišemo: <?php echo $number->currency ('98765.43 .765. Apr 29th 2013.3). ?> 32 .dayAsSql Ako napišemo: <?php echo $time->daysAsSql('07-11-2007 18:13:15'. NumberHelper .precision Ako napišemo: <?php echo $number->precision('123456789'.'07-01-2007 00:00:00'.'').457 .77% .2. ?> CakePHP vraća true ako je dati datum današnji 3. ?> CakePHP vraća: (created >= '2013-04-29 00:00:00') AND (created <= '2012-06-29 23:59:59') .7654321'. 18:13 .toPercentage Ako napišemo: <?php echo $number->toPercentage('98. ?> CakePHP vraća: 123.4321'.CakePHP vraća: Mon.'.2). ?> CakePHP vraća: 98.

43 .765. file php klase trebao bi izgledati ovako: Putanja: /app/views/helpers/link.CakePHP vraća: 98. Ako koristimo output ( ) za formatiranje naših linkova title i URL i vraćanje ih u view. boolean $return = false. npr..4.php = name of php file in /app/views/helpers Ako želimo kreirati helper koji će koristiti output CSS style linka koji trebamo u aplikaciji.php class LinkHelper extends Helper { function makeEdit($title. ?> CakePHP vraća: 117. u link.php trebamo dodati: Putanja: /app/views/helpers/link. prvo trebamo kreirati novu klasu u app/views/helpers..toReadableSize Ako napišemo: <?php echo $number->toReadableSize('123456789').74 MB 3.php class LinkHelper extends Helper { function makeEdit($title. $url) { // Logic to create specially formatted link goes here. string $string. Kreiranje vlastitih helpera Konvencija imenovanja je slična kao u modelu: • • • LinkHelper = class name link = key in helpers array link. } } Postoji nekoliko funkcija uključenih u Cake helper klase koje možemo iskoristiti: output ( $string. da se naš helper zove LinkHelper. $url) { 33 . $return = false).

php ( korištenje drugi helpera) class LinkHelper extends Helper { var $helpers = array('Html'). Scaffolding Scaffolding je dobar način uzimanja početnog dijela web aplikacije. scaffolding je uključen u Cake. function makeEdit($title. Scaffolding analizira tabele u bazi i kreira standradnu listu sa add. Da bi smanjili pritisak na developere. delete i edit dugmićima. class ThingsController { var $helpers = array('Html'. $url) { // Use the HTML helper to output // formatted data: $link = $this->Html->link($title. } } return $this->output("<div class=\"editOuter\">$link</div>"). Putanja: /app/views/helpers/link. Strandardnu formu za edit i standardni 34 . Da koristimo prednosti drugi helpera trebamo specificirati helper koji želimo koristiti. Web developeri ne vole kreirati forme koje nikad neće “stavrno” koristiti. 'Link'). $url. } } Možemo koristiti neke već postojeće funkcionalnosti drugog helpera.// Use the helper's output function to hand formatted // data back to the view: return $this->output("<div class=\"editOuter\"><a href=\"$url\" class=\"edit\">$title</a></div>"). početna šema baze i tema se mijenjaju. zatim pišemo funkcije kao u controller-u npr. npr. $helper nizom. } 4. što je normalno u početnom dijelu dizajna procesa. Kada kreiramo vlastiti helper snimimo ga u /app/views/helpers/ i bit ćemo u mogućnosti da ga uključimo u naš contoller-u preko specijalne varijable $helpers. array('class' => 'edit')).

} ?> Scaffold očekuje da bilo koje ime polja koje završava sa “_id” je strani ključ za tabelu koja ima ime koje predhodi “ _”. Data Validation Kreiranje korisnički validacijski pravila pomaže da podaci budu sigurni u Model-u uz pravila aplikacije. Ako imamo tabelu category i kolonu parent_id.scaffold. parent_id je strani ključ za tabelu parent. primjer: 35 . Kada imamo strani ključ u tabeli (tabela title sa category_id) i imamo asocijaciju modela. 5.thtml /app/views/posts/scaffold/new. Da postavimo koje polje u stranoj tabeli će se prikazivati.thtml /app/views/posts/scaffold/edit. Da bi to uradili koristimo Model:: validate array u Model definiciji. u controller treba dodati varijablu $scaffold. lozinka može biti samo osam karaktera.view za provjeru jednostavni item-a u bazi. Bake dozvoljava da generiramo kodiranu verziju scaffold koda.scaffold. Ako želimo nešto drugačiji scaffolding view. korisničko ime može sadržavati samo slova. npr. Za dodavanje scaffoldinga u aplikaciju. Primjer: <?php class Title extends AppModel { var $name = 'Title'. Primjer: Custom scaffolding views for a PostsController should be placed like so: /app/views/posts/scaffold/index. } ?> var $displayField = 'title'. postavimo varijablu $displayField u strani model.thtml Osobina koja može koristiti u Cake-u je kod generator: Bake.thtml /app/views/posts/scaffold/show. označeni box će se automatski popuniti sa redom iz strane tabele (category) u show/edit/new view. možemo kreirati vlastiti.scaffold. koju zatim možemo premjestiti i modifikaovati prema zahtijevima aplikacije.scaffold. <?php class CategoriesController extends AppController { var $scaffold.… Prvi korak u data validation je kreiranje validacijskih pravila u Model-u. npr.

'). Will Robinson. 'email' => VALID_EMAIL. to su: VALID_NOT_EMPTY VALID_NUMBER VALID_EMAIL VALID_YEAR Ako postoji bilo koja validacija u modelu definicije ($validate array) bit će analizirana i provjerena tokom snimanja (Model:: save() method).Putanja: /app/models/user. Validation errors. var $validate = array( 'login' => '/[a-z0-9\_\-]{3. function add () { if (empty($this->data)) { $this->render(). 'password' => VALID_NOT_EMPTY. } ?> Validacije se definišu koristeći Perl kompatibilne regularne ekspresije. primjer: /app/controllers/blog_controller. } 36 .}$/i'. the stuff is valid } else { //Danger. $this->set('errorMessage'.php. } else { if($this->Post->save($this->data)) { //ok cool. Ali su obično podaci u kodu controller-a.php <?php class BlogController extends AppController { var $uses = array('Post'). $this->render(). Za direktnu validaciju podataka koristimo Model::validates() (vraća false ako su podaci pogrešni) i Model::invalidFields() (vraća error message kao niz).php <?php class User extends AppModel { var $name = 'User'. 'born' => VALID_NUMBER ). 'Please correct errors below. neke od njih su predefinisane u /lib/validators.

logout. u file-u trebamo definirati klasu koja ima ime isto kao i komponenta. array('size'=>'40'))?> <?php echo $html->tagErrorMsg('Post/title'. 'Body is required.thtml <h2>Add post to blog</h2> <form action="<?php echo $html->url('/blog/add')?>" method="post"> <div class="blog_add"> <p>Title: <?php echo $html->input('Post/title'.$model…] se koristi za provjeru bilo koje korisničke validacije dodane u model. ako želimo login.) to je biznis logika. Ako želimo dodati još neke unose na glavni meni kada je korisnik logiran. var $controller = true. tako da je ova autentifikacija komponenta. 6. Developeri više vole koristiti specijalne funkcije koje prave u komponentama. tako što dodamo file u /app/controllers/components direktorij. Components Komponente se koriste kao pomagalo controller-a u specifičnim situacijama... 37 .')?> </p> <p>Body <?php echo $html->textarea('Post/body') ?> <?php echo $html->tagErrorMsg('Post/body'. 'Title is required. tako da može biti prikazana sa tagErrorMsg() u view-u. to je prezentacijska logika. Primjer: autentifikacija korisnika. Možemo kreirati i vlastite komponente. Komponente su za controller. Glavna razlika u tome je što komponente enkapsuliraju biznis logiku. isto što i helper za view. add. permisije za korisnike (akcije: edit.} } ?> } View za blog može izgledati ovako: /app/views/blog/add. del. nego proširene Cake biblioteke. Primjer: ako želimo kreirati komponentu foo. dok helperi enkapsuliraju prezentacijsku logiku.php. class FooComponent extends Object { var $someVar = null. Controller::validationErrors() vraća bilo koju poruku o grešci u modelu.')?> </p> <p><?=$html->submit('Save')?></p> </div> </form> Controller::validates($model[.

jednostavno u našoj komponenti deklariramo koju komponentu želimo kotistiti.function startup(&$controller) { // This method takes a reference to the controller which is loading it. Sada u controller-u možemo koristiit: $this->Foo->doFoo(). Možemo koristiti i druge komponente u vlastitoj komponenti. Komponente imaju dozvolu controller-a da se učitaju u startup() metodi. Ovo nam dozvoljava da postavimo osobine komponente u beforeFilter() metodu koja komponenta će se izvršiti u startup() metodi. Da bi koristili naš model u komponenti. // Perform controller initialization here. trebamo kreitati instancu: $foo =& new Foo(). } function doFoo() { $this->someVar = 'foo'. } } Da bi koristili komponentu trebamo dodati sljedeći kod u definiciju controller-a var $components = array('Foo'). Primjer: ako želimo koristiti session komponentu: var $components = array('Session'). 38 . ova metoda se poziva poslije Controller::beforeFilter().

Tabele servisa Servis se sastoji od pet tabela: 39 .1.7. Servis “Anketa” 7.

jedna anketa može imati samo jedno pitanje. broj korisnika koliko ih je glasalo i broj odgovora (noq) tj. jer jedna anketa može imati više rezultata. 40 .1. broj odgovora koliko će imati anketa.u tabelu „polls“ smještamo id (primarni ključ tabele). a 1:n u odnosu na tabelu „results“. ukupno odgovora (ukupno_odg) tj. polls . Tabela „polls“ je u odnosu 1:1 sa tabelom „questions“ tj. ime ankete (name).

2. lang_id i pitanje 4. služi za snimanje IP adresa. odgvore (odgovor).2. U koloni rezultat smješteni su rezultati odgovora koji se vežu za jednu anketu „anketa_id = 1“. rezultati_id povezuje sa tabelom “results” u odnosu 1:1. lang_id povezuje tabelu “answers” sa tabelom ”languages” u odnosu 1:n. Tabelu answers pitanja_id povezuje sa tabelom “questions” u odnosu 1:n. tj. jedno pitanje može imati više odgovora. svaki rezultat (odgovor) povezan je sa anketom. polje anketa_id povezuje tabelu „questions“ sa tabelom „polls“ u odnosu 1:1. ukupan broj glasova za svaki odgovor. jedan odgovor može imati samo jedan rezultat. Tabela „checkip“ nije povezana sa drugim tabelama. checkip – u tabelu „checkip“ smještamo id (primarni ključ tabele). results – u tabelu „results“ smještamo id (primarni ključ tabele). id 1 2 3 4 rezultat 15 10 20 8 anketa_id 1 1 1 1 5. Na osnovu ove tabele provjeravamo da li je korisnik već glasao i poll_code.tj. kao što možemo vidjeti u sljedećem primjeru. 41 . odgovore možemo pisati na više jezika. rezultat tj.php class Question extends AppModel { var $name = 'Question'. questions – u tabelu „questions“ smještamo id (primarni ključ tabele). 3. question. polje anketa_id povezuje sa tabelom „polls“ u odnosu 1:1. 7. answers – u tabelu „answers“ smještamo id (primarni ključ tabele). IP adresu korisnika. MVC servisa 7.2. Model Servis „Anketa“ ima četiri modela: 1.1.

2. } 7. View Servis „Anketa“ ima view „polls“ koji se sastoji od četiri php file-a: 1. } 3. admin_index. } 4.php class Answer extends AppModel { var $name = 'Answer'.php class Result extends AppModel { var $name = 'Result'. answer. result.} 2. poll.php class Poll extends AppModel { var $name = 'Poll'.php <tr> <td colspan="3"><table width="100%" border="0" cellspacing="10" cellpadding="0"> <tr> <td><span class="naslov_kategorije"><strong>Kreirati:</strong></span><br /> 42 .2.

array('size' => '10'))?></td> <td><?php //echo $html->tagErrorMsg('Poll/noq.php <tr> <td colspan="3"><form method="post" action="<?php echo $rootPath. koji view i php file se poziva.'/Polls/admin_add_an'.php file-u kreirali smo formu koja pomoću metode post unesene podatke šalje admin_add_qu.php <tr> <td colspan="3"><form method="post" action="<?php echo $rootPath.<a class="menu" href="<?php echo $rootPath ?>Polls/admin_add/"> [Dodati novu anketu]</a><br /></td> </tr> </table></td> </tr> Admin_index. admin_add. 2. Kreiramo tabelu i u redove/kolone tabele dodajemo link zbog preglednosti na stranici. array('size' => '40'))?> <?php //echo $html->tagErrorMsg('Poll/ name'. zatim pišemo putanju tj.') ?> </td> </tr> <tr> <td>Broj odgovora:</td><td><?php echo $html->input('Poll/noq'. Dodali smo i css class „naslov_kategorije“ za ispis „Kreirati“ i class „menu“ za link [Dodati novu anketu]. u našem primjeru Polls/admin_add/.?>" enctype="multipart/form-data" > <?php 43 .php file-u.admin_add_qu. Koristili smo html helpere za poruku u slučaju greške koje smo poslije stavili pod komentar jer nisu bili neophodni i html helper za snimanje (prosljeđivanje) tj. Zatim smo kreirali tabelu i koristili smo html helpere za unos naziva ankete i broj odgovora u anketi. dugmić „Save“.php služi za ispis linka za dodavanje nove ankete.') ? ></td> <tr> <td><?php echo $html->submit('Save') ?></td> </tr> U admin_add.?>" enctype="multipart/form-data" > <table width="100%" border="0" cellspacing="10" cellpadding="0"> <tr> <td><table> <tr> <td>Pitanje</td> <td><?php echo $html->input('Poll/name'. 'Name is required.'/Polls/admin_add_qu'. 'Br_odg is required. 3.

echo $html->hidden('Pages_'.</td> </tr> <tr> <td height="25" bgcolor="#E1E1E1">&nbsp.</td> <td bgcolor="#E1E1E1">Odgovori:</td> <td bgcolor="#E1E1E1">&nbsp.foreach ($langs as $lang){ echo $html->hidden('Pages_'. ?></td> </tr> </table></td> <td bgcolor="#666666">&nbsp. array('value' => $lang['Language']['id'])).</td> <td bgcolor="#666666" class="naslov_kategorije"><table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td width="4%"><img src="<?php echo $imgPath.$lang['Language'] ['code']. $lang['Language']['code']. ?> <table width="700" border="0" align="center" cellpadding="0" cellspacing="0"> <tr> <td width="12" height="25">&nbsp.</td> <td bgcolor="#E1E1E1">&nbsp.</td> <td bgcolor="#E1E1E1"> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <?php $j =1.</td> <td bgcolor="#E1E1E1">&nbsp.'/poll_id'. foreach($pitanja as $pitanje){?> <tr> 44 . array('value' => $poll_id)).$lang['Language']['code']. ?>lang/<?php echo $lang['Language']['code'].'/lang'.gif" alt="" /></td> <td width="96%" class="atributi_p"><?php echo $lang['Language'] ['name'].'/question'.</td> <td width="329">&nbsp. array('size' => '80'))?></td> <td bgcolor="#E1E1E1">&nbsp.</td> </tr> <tr> <td height="25" bgcolor="#E1E1E1">&nbsp.</td> </tr> <tr> <td height="25" bgcolor="#E1E1E1">&nbsp.</td> <td bgcolor="#E1E1E1" class="naslov_kategorije">Pitanje:</td> <td bgcolor="#E1E1E1">&nbsp. ?>.</td> <td bgcolor="#E1E1E1"><?php echo $html->input('Pages_'.</td> </tr> <tr> <td height="25" bgcolor="#E1E1E1">&nbsp.</td> <td width="9">&nbsp.</td> </tr> <tr> <td height="25" bgcolor="#E1E1E1">&nbsp.</td> </tr> <tr> <td height="35" bgcolor="#666666">&nbsp.

html helper za snimanje/submit i html helper za unos/input.'/answer_'.$j.<td width="5%"><?php print $j. prvu foreach petlju koristimo da izlistamo sve jezike koji se nalaze u bazi. }?> </table></td> <td bgcolor="#E1E1E1">&nbsp. Drugu foreach petlju koristimo da ispišemo pitanja i odgovore na svim jezicima.$lang['Language']['code'].Koristimo html helper za skriveno „hidden“ polje za provjeru poll_id i coda za jezik. tj.admin_add_an. Tabeli smo u neke redove i kolone dodali boju pozadine. ?></td> <td width="95%"><?php echo $html->input('Pages_'. id tabele „languages“.</td> <td width="454"></td> </tr> <tr> <td height="35" bgcolor="#666666">&nbsp. array('value' => $pitanja[$j-1])).php file-u. ?></td> </tr> </table> </form></td> </tr> U file-u admin_add_qu.php napravili smo formu koja pomoću metode post unesene podatke prosljeđuje admin_add_an. array('size' => '80')).$j..'/result_id_'. zastavu pored naziva jezika. echo $html->hidden('Pages_'. Imamo dvije foreach petlje.</td> </tr> </table> <?php }?> <table width="700" border="0" align="center" cellpadding="0" cellspacing="0"> <tr> <td width="12" height="25">&nbsp.</td> <td bgcolor="#666666"><?php echo $html->submit('Save'). ?> </td> </tr> <?php $j++. 4..</td> <td bgcolor="#E1E1E1">&nbsp.</td> <td bgcolor="#E1E1E1">&nbsp. $lang['Language']['code'].</td> </tr> <tr> <td height="25" bgcolor="#E1E1E1">&nbsp. css class.php <tr> <td colspan="3"><table width="100%" border="0" cellspacing="10" 45 .

php. 7. } Kada kreiramo controller . results_controller. Controller Servis „Anketa“ ima četiri controller-a: polls_controller.dnc.cellpadding="0"> <tr> <td><h1>Hellooooo!!!!<br /></h1></td> </tr> </table></td> </tr> <tr> <td colspan="3"><a class="menu" href="<?php echo $rootPath ? >Polls/admin_add/"><h5>Povratak na stranicu za kreiranje ankete</h5i></a></td> </tr> <tr> <td colspan="3" align="center"><span class="copy">Copyright &copy. answers_controlller.php Kreiranje controller-a: class PollsController extends AppController { var $name = 'Polls'.php.php. u contoller-u pišemo funkcije koje se izvršavaju u view-u. dodali smo u kolonu i poruku „Hellooooo!!!!“. polls_controller.php i questions_controller. dodamo funkcije koje su nam potrebne: 46 .2. 2007 <a href="http://www.php kreirali smo tabelu i link za povratak na stranicu za kreiranje ankete. 1. zatim imamo link za DNC stranu.ba" target="_blank">DNC Spinnaker</a></span></td> </tr> </table></td> </tr> U file-u admin_add_an.3.

'0'. $this->Poll->query("INSERT INTO polls(`id`. function admin_add_qu() { $this->set('rootPath'. if($id == 0) $id = 1. for($i=0. 'http://localhost/cake/img/'). $this->requestAction('/languages/getLanguages')). if ($this->data) { $n = $this->data. odnosno loga i slika potrebnih za bolji dizajn strane. $pitanje_id[$i] = $id. $i++){ $id += $i. } Funkcija admin_index() služi za kreiranje varijeble za prikaz početne strane. if($id == 0) $id = 1. $ret = $ret[0]. `ukupno_odg`. $i<$noq.`noq`) VALUES('$polls_id'. $ret = $this->Poll->query("SELECT max(id) FROM polls"). } $this->set('langs'. $polls_id = $id.'$noq')"). $noq=$n['Poll']['noq']. $ret = $this->Poll->query("SELECT max(id) FROM results"). '$polls_id')"). `rezultat`. $id = $ret[0]['max(id)']+1. `anketa_id`) 47 . 'http://localhost/cake/'). $ret = $ret[0]. results(`id`. 'http://localhost/cake/img/'). '0'. $name=$n['Poll']['name']. U našem primjeru kreiramo varijablu $rootPath.`name`. $id = $ret[0]['max(id)']+1. u našem primjeru 'http://localhost/cake/' i kreiramo varijablu $imgPath za prikaz slika.'$name'. $this->Poll->query("INSERT INTO VALUES('$id'.function admin_index() { $this->set('rootPath'. $this->set('imgPath'. koju zatim pozivamo u funkcijama kada trebamo prikazati početnu stranu. 'http://localhost/cake/'). $this->set('imgPath'.

$i])){ $odgovor = $podaci['answer_'. if($id == 0) $id = 1. $polls_id). '{$podaci['lang']}'.$i]. `pitanja_id`. $result_id = $podaci['result_id_'. '{$podaci['lang']}'. `lang_id`. $questions_id = $id. `pitanje`)VALUES('$questions_id'. $this->set('pitanja'. } $questions_id++. $this->Poll->query("INSERT INTO answers (`odgovor`. 'http://localhost/cake/'). function admin_add_an() { $this->set('rootPath'. '$questions_id'. `rezultati_id`) VALUES('$odgovor'. if ($this->data) { $ret = $this->Poll->query("SELECT max(id) FROM questions"). $ret = $ret[0]. `anketa_id`. `lang_id`. $pitanje_id). foreach($this->data as $podaci){ $this->Poll->query("INSERT INTO questions (`id`.$i]. $id = $ret[0]['max(id)']+1. } } 48 .'{$podaci['poll_id']}'.'{$podaci['question']}')"). $i++. ime ankete i broj odgovora. $this->set('imgPath'. '$result_id')"). ukupan broj glasova za svaki odgovor i anketa_id da poveže odgovore sa anketom.$this->set('poll_id'. while(isset($podaci['answer_'. } } Funkciaj admin_add_qu() provjerava da li postoje podaci. $i = 1. Zatim uzima max id od „results“ i prolazi kroz for petlju onoliko puta koliko ima odgovora u anketi i u tabelu „results“ unosi rezultat tj. 'http://localhost/cake/img/'). Ako postoje podaci uzima max id tabele polls i u tabelu polls smješta podatke tj.

} Funkcija rezultati() vraća broj glasova za svaki odgovor u anketi.rezultat.odgovor FROM answers a.id AND a. function ukupnoGlasaova($id ){ $page = array ('id' => $id). id jezika i pitanje u tabelu „questions“. $ukupno = $this->Poll->findAll($page). a. prolazi kroz foreach petlju dok postoje podaci. 49 . return $ukupno[0]['Poll']['ukupno_odg']. } Funkcija ukupnoGlasaova() vraća ukupan broj glasova i ispisuje ih na indexnu stranu.Funkcija admin_add_an() provjerava da li postoje podaci. results_controller. function rezultati($id ){ return $this->Poll->query("SELECT r. Varijabla $i se inkrementuje onoliko puta koliko imamo odgovora i $questions_id onoliko puta koliko ima jezika u bazi. upisuje id ankete. Ako postoje podaci uzima max id iz tabele „questions“.php class ResultsController extends AppController { var $name = 'Results'. id pitanja i id rezultata upisuje u tabelu „answers“. tj. na stranu gdje se nalazi anketa. id jezika. } function inc($id) { $page = array ('id' => $id).rezultati_id = r. a odgovore.lang_id = '$id'"). 2. results r WHERE a.

if(isset($_COOKIE['DNCCMSANKETA'])) $uslov2 = false. $this->Result->query("UPDATE `results` SET `rezultat` = `rezultat` + 1 WHERE id = '$rez_id'"). vrši update tabele results i polls. } Funkcija inc() inkrementuje broj odgovora i ukupan broj glasova.$rez = $this->Result->findAll($page). $this->Result->query("INSERT INTO checkip (`IP`. "glasao"). Pomoću funkcije getenv() uzima ip adrese korisnika i snima ih u tabelu “checkip“ i postavlja cookie ako je korisnik glasao. `poll_code`) VALUES ('$ip_'. else $uslov2 = true. else $uslov1 = true. tj. $this->Result->query("UPDATE `polls` SET `ukupno_odg` = `ukupno_odg` + 1 "). $ip_ = getenv ("REMOTE_ADDR"). ' ')"). function provjeraGlasanja(){ $ip_ = getenv ("REMOTE_ADDR"). $rez_id = $rez[0]['Result']['id']. 50 . setcookie("DNCCMSANKETA". if($this->Result->query("SELECT poll_code FROM checkip WHERE IP = '$ip_'")) $uslov1 = false.

Ako ima IP ili cookie ili oboje. a ako ne prikazuje anketu i korisnik ima mogućnost glasanja.php class QuestionsController extends AppController { var $name = 'Questions'.rezultati_id FROM answers a. questions q WHERE q. pitanje_id i land_id.php file-u. 4.if(($uslov1) && ($uslov2)) return true.anketa_id = 1 AND q.lang_id = 1').odgovor. a.php <?php class AnswersController extends AppController { var $name = 'Answers'. answers_controller. 'http://localhost/cake/img/'). } } ?> Controller answers ima samo funkciju odgovori() koja vraća odgovore i rezultati_id koji su povezani preko anketa_id. Ova funkcija se poziva u /pages/display. land_id = 1 i pitanje_id u tabeli „answers“odgovara id-u u tabeli „questions“. prikazuje reultate ankete. } Funkcija provjeraGlasanja() provjerava da li postoji korisnikova IP adresa u bazi i da li ima cookie. function odgovori() { return $this->Answer->query('SELECT a. U našem primjeru anketa_id = 1. else return false. 'http://localhost/cake/'). function admin_index() { $this->set('rootPath'. 3. questions_controller. } function view($id = null) { 51 .id = a. $this->set('imgPath'.pitanja_id AND a.

funkcija view() služi za prikaz pitanja na osnovu id-a. Administrator kreira anketu. 'lang_id' => $lang_id). $pitanje = $this->Question->findAll($pitanje). } Funkcija pitanje() služi za ispis pitanja na indexnoj strani. Na admin strani kliknemo na link anketa 52 . Upustvo za servis Servis „Anketa“ sa aspekta adminstracijskog dijela treba da omogući kreiranje. function pitanje($id = 1. editovanje i brisanje ankete. $this->set('questions'. $lang_id = 1) { $pitanje = array ('anketa_id' => $id. Funkcija admin_index() ima istu funkciju kao u controller-u polls.php file-u. $this->Question->read()). unosi naziv ankete i broj odgovora.} } $this->Question->id = $id. zatim pitanje i odgovore. 1. return $pitanje[0]['Question']['pitanje']. 7. 3. poziva se u /pages/display.

Admin unosi naziv ankete npr. Zatim se otvara nova strana sa linkom za dodavanje nove ankete 3.2.1 i broj odgovora u anketi 53 . anketa br.

na sve jezike koje ima u bazi 54 . Admin unosi pitanje i odgovore na više jezika tj.4.

55 .

kreiranje ankete . engleski. korisniku se prikazuju rezultati ankete Ključne mogućnosti servisa: .Sa aspekta korisnika: 1. Servis je implementiran na 3 jezika (bosanski. Korisnik posjećuje stranu 2. prikažu se rezultati glasanja 4. nakon klika na dugmić “glasati“. Ako nema u bazi IP adrese i korisnik nema cookie-a. Servis treba biti dostupan 24h svih sedam dana u 56 . njemački) i ima mogućnost dodavanja novi jezika.glasanje . prikazuje mu automatski rezultat ankete.pregled rezultata ankete Aplikacija dozvoljava da korisnik može samo jednom glasati na anketi na osnovu IP adrese i cookie-a. Ako postoji IP adresa ili cookie. dozvoljava se glasanje. Ako korisnik pokuša drugi put glasati. Provjerava se korisnikova IP adresa i cookie 3.

Admin kreira anketu na admin dijelu 2. Anketa je prikazana na prezentacijskom dijelu Naziv: Cilj: Prioritet: Grupa korisnika: CASE2 . Servis nam treba omogućiti da dobijemo mišljenje korisnika o anketi „temi“ koju postavimo.sedmici. Izvještaji: prezentacijski dio administracijski dio Usability: Lakoća korištenja servisa.glasanje Glasati na anketi Visok Gost 57 .kreiranje ankete Kreirati anketu da bi dobili informacije od korisnika Visok Admin Server mora biti online 1. potrebno je osnovno poznavanje rada na računaru ( za kriranje ankete) i mogućnosti i znanje korištenja interneta ( za glasanje i pregled rezultata) Kategorije korisnika: obični korisnici (imaju mogućnost glasanja i pregleda rezultata) administratori ( imaju mogućnost kreiranja ankete) CASE: Naziv: Cilj: Prioritet: Grupa korisnika: Preduslovi: Scenario: CASE1.

Gost posjećuje stranicu 2. posjetiti stranicu za pregled rezultata Za gosta: 1. da nije prije glasao 1. posjetiti stranicu. gost Server mora biti online.Preduslovi: Scenario: Server mora biti online. za pregled treba kliknuti da dugnić „Pregled rezultata“ 3. Gost posjećuje stranicu 2. Provjera gostove IP adrese i cookie-a. Analizira rezultate ankete Dokumentacija seminarskog: osnove CakePHP-a opis aplikacije MVC za servis „Anketa“ case-ovi servisa „Anketa“ 58 . Na osnovu IP adrese i cookie-a daje se mogućnost glasanja ili pregleda rezultata Naziv: Cilj: Prioritet: Grupa korisnika: Preduslovi: Scenario: CASE3 – pregled rezultata Pregledati rezultate glasanje ankete Visok Admin. 3. Ako je već glasao. automatski se prikazuju rezultati Za admina: 1. Posjećuje stranicu 2. gost mora biti online. korisnik mora biti online. Ako prvi put glasa.

brute force protection.. Jedini nedostatak koji sam pročitala tokom istraživanja za seminarski je što se do sada core cakephp mijenjao nekoliko puta. Primjenom frameworka se dobiva mnogo prednosti: portabilnost.. MVC framework možemo iskoristiti za većinu aplikacija u PHP-u koje želimo na brzinu iskodirati i time sami sebi uštediti vrijeme i natjerati se da se držimo nekih standarda. bez gubljenja fleksibilnosti. CakeAMFPHP za vezivanje na flash. xcache. Svi MVC framework-ovi nastoje zadovoljiti iste ciljeve i u MVC filozofiji su pravila jasna. CakePHP developri rade na razvoju preko 100 projekata. no s verzijom 2. pagination.a uskoro bi trebao dobiti podršku za I18 arhitekturu.0 bi se to trebalo stabilizirati. ubrzanje razvoja. tako da ni implementacija ne može biti toliko različita.… . automatic layout switcher. html helperi. MVC arhitektura pomaže sa čistim predstavljanjem database funkcijalnosti. ajax.Primarni cilj seminarskog je bio predstaviti strukturu frameworka koji omogućava PHP korisnicima svih nivoa brzi razvoj web aplikacija. 59 ...ZAKLJUČAK CakePHP je framework koji ima podršku za puno stvari kao što je memcache. stabilnost... biznis logike i prezentacije. Cakephp ima napravljene komponente za autentifikaciju.višejezičnost.

org 60 .cakephp.LITERATURA 1. SPIS 2006 Cakesheet Web stranice: www.org www. 4.forgephp.org http://bakery.cakephp.org www.org www. RI. 2.wikipedia. 3. CakePHP Manual PHP Design Patterns ETF Sarajevo.mi3dot.

Sign up to vote on this title
UsefulNot useful