├── .gitmodules
├── phpunit.xml
├── test
├── lib
│ ├── bootstrap.php
│ └── testcase.php
├── LibraryTest.php
├── QueryTest.php
└── ItemTest.php
├── bootstrap.php
├── library
├── query.php
├── library.php
└── item.php
└── readme.md
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "toolkit"]
2 | path = toolkit
3 | url = https://github.com/getkirby/toolkit.git
4 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | test
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/lib/bootstrap.php:
--------------------------------------------------------------------------------
1 | __DIR__ . DS . 'library' . DS . 'library.php',
11 | 'library\\item' => __DIR__ . DS . 'library' . DS . 'item.php',
12 | 'library\\query' => __DIR__ . DS . 'library' . DS . 'query.php',
13 | ));
--------------------------------------------------------------------------------
/test/lib/testcase.php:
--------------------------------------------------------------------------------
1 | root);
12 |
13 | // set up a new library
14 | $this->library();
15 |
16 | }
17 |
18 | protected function tearDown() {
19 | dir::remove($this->root);
20 | }
21 |
22 | public function library() {
23 | return $this->library = new Library($this->root);
24 | }
25 |
26 | public function item() {
27 | return new Library\Item($this->library, 'test');
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/test/LibraryTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($this->library->root(), $this->root);
10 | }
11 |
12 | public function testIsWritable() {
13 | $this->assertTrue($this->library->isWritable());
14 | }
15 |
16 | public function testDatabase() {
17 | $this->assertInstanceOf('Database', $this->library->database());
18 | }
19 |
20 | public function testIndex() {
21 | $this->assertInstanceOf('Database\\Query', $this->library->index());
22 | }
23 |
24 | public function testFolder() {
25 |
26 | // test the folder object
27 | $this->assertInstanceOf('Folder', $this->library->folder());
28 | $this->assertEquals($this->library->folder()->root(), $this->library->root());
29 |
30 | }
31 |
32 | public function testId() {
33 |
34 | $this->assertTrue(is_string($this->library->id()));
35 | $this->assertTrue(strlen($this->library->id()) == 32);
36 |
37 | }
38 |
39 | public function testColumns() {
40 |
41 | $columns = array(
42 | 'id',
43 | 'type',
44 | 'status',
45 | 'created',
46 | 'updated'
47 | );
48 |
49 | $this->assertEquals($this->library->columns()->pluck('name'), $columns);
50 |
51 | }
52 |
53 | public function testCreate() {
54 |
55 | $item = $this->library->create('todo', array(
56 | 'text' => 'Test this thing'
57 | ));
58 |
59 | $this->assertInstanceOf('Library\\Item', $item);
60 |
61 | }
62 |
63 | public function testItem() {
64 |
65 | $item = $this->library->item(array(
66 | 'id' => $this->library->id(),
67 | 'type' => 'test',
68 | 'status' => 'draft',
69 | 'updated' => time(),
70 | 'created' => time(),
71 | ));
72 |
73 | $this->assertInstanceOf('Library\\Item', $item);
74 |
75 | }
76 |
77 | public function testDelete() {
78 |
79 | $item = $this->library->create('todo');
80 | $id = $item->id();
81 |
82 | // check if the item can be found
83 | $this->assertInstanceOf('Library\\Item', $this->library->find($id));
84 |
85 | // now delete it
86 | $item->delete();
87 |
88 | // and check again
89 | $this->assertFalse($this->library->find($id));
90 |
91 | }
92 |
93 | public function testRebuild() {
94 |
95 | $a = $this->library->create('test');
96 | $b = $this->library->create('test');
97 |
98 | // delete the index
99 | f::remove($this->library->root() . DS . 'library.sqlite');
100 |
101 | $this->library->rebuild();
102 |
103 | $this->assertEquals(2, $this->library->count());
104 | $this->assertInstanceOf('Library\\Item', $this->library->find($b->id()));
105 |
106 | }
107 |
108 | }
--------------------------------------------------------------------------------
/test/QueryTest.php:
--------------------------------------------------------------------------------
1 | library->create('todo', array('text' => 'first todo'));
11 | $this->library->create('todo', array('text' => 'second todo', 'status' => 'public'));
12 |
13 | // create some articles
14 | $this->library->create('article', array('text' => 'first article', 'created' => strtotime('2012-12-12 22:33')));
15 | $this->library->create('article', array('text' => 'second article', 'status' => 'public'));
16 | $this->library->create('article', array('text' => 'third article', 'status' => 'private'));
17 |
18 | }
19 |
20 | public function testType() {
21 |
22 | $this->dummyData();
23 |
24 | $this->assertEquals(2, $this->library->type('todo')->count());
25 | $this->assertEquals(3, $this->library->type('article')->count());
26 |
27 | }
28 |
29 | public function testStatus() {
30 |
31 | $this->dummyData();
32 |
33 | $this->assertEquals(2, $this->library->status('draft')->count());
34 | $this->assertEquals(2, $this->library->status('public')->count());
35 | $this->assertEquals(1, $this->library->status('private')->count());
36 |
37 | }
38 |
39 | public function testYear() {
40 |
41 | $this->dummyData();
42 |
43 | $this->assertEquals(1, $this->library->year(2012)->count());
44 | $this->assertEquals(4, $this->library->year(date('Y'))->count());
45 |
46 | }
47 |
48 | public function testMonth() {
49 |
50 | $this->dummyData();
51 |
52 | $this->assertEquals(1, $this->library->month('2012-12')->count());
53 | $this->assertEquals(4, $this->library->month(date('Y-m'))->count());
54 |
55 | }
56 |
57 | public function testDay() {
58 |
59 | $this->dummyData();
60 |
61 | $this->assertEquals(1, $this->library->day('2012-12-12')->count());
62 | $this->assertEquals(4, $this->library->day(date('Y-m-d'))->count());
63 |
64 | }
65 |
66 | public function testSearch() {
67 |
68 | $this->dummyData();
69 | $this->assertEquals(3, $this->library->search('article')->count());
70 |
71 | }
72 |
73 | public function testCount() {
74 |
75 | $this->dummyData();
76 | $this->assertEquals(5, $this->library->count());
77 |
78 | }
79 |
80 | public function testYears() {
81 |
82 | $this->dummyData();
83 | $this->assertEquals($this->library->years(), array(2012, date('Y')));
84 |
85 | }
86 |
87 | public function testMonths() {
88 |
89 | $this->dummyData();
90 | $this->assertEquals($this->library->months(), array('2012-12', date('Y-m')));
91 |
92 | }
93 |
94 | public function testDays() {
95 |
96 | $this->dummyData();
97 | $this->assertEquals($this->library->days(), array('2012-12-12', date('Y-m-d')));
98 |
99 | }
100 |
101 | }
--------------------------------------------------------------------------------
/library/query.php:
--------------------------------------------------------------------------------
1 | library = $library;
14 | parent::__construct($library->database, 'items');
15 | }
16 |
17 | protected function query($query, $params = array()) {
18 |
19 | $result = parent::query($query, $params);
20 |
21 | if(is_a($result, 'Collection')) {
22 | $library = $this->library;
23 | $result = $result->map(function($item) use($library) {
24 | return $library->item((array)$item);
25 | });
26 | return $result;
27 | } else if(is_a($result, 'Obj')) {
28 |
29 | if(isset($result->aggregation)) {
30 | return $result;
31 | } else {
32 | return $this->library->item((array)$result);
33 | }
34 |
35 | } else {
36 | return $result;
37 | }
38 |
39 | }
40 |
41 | public function find($id) {
42 | return $this->where('id', '=', $id)->one();
43 | }
44 |
45 | public function type($type) {
46 | return $this->where('type', '=', $type);
47 | }
48 |
49 | public function day($day) {
50 |
51 | if(!preg_match('!^[\d]{4}-[\d]{2}-[\d]{2}$!', $day)) {
52 | throw new Exception('Invalid day format. Must be YYYY-MM-DD');
53 | }
54 |
55 | $start = strtotime($day);
56 | $end = strtotime('+1 day', $start);
57 | return $this->where('created', '>=', $start)->where('created', '<', $end);
58 |
59 | }
60 |
61 | public function month($month) {
62 |
63 | if(!preg_match('!^[\d]{4}-[\d]{2}$!', $month)) {
64 | throw new Exception('Invalid month format. Must be YYYY-MM');
65 | }
66 |
67 | $start = strtotime($month . '-01');
68 | $end = strtotime('+1 month', $start);
69 | return $this->where('created', '>=', $start)->where('created', '<', $end);
70 |
71 | }
72 |
73 | public function year($year) {
74 |
75 | if(!preg_match('!^[\d]{4}$!', $year)) {
76 | throw new Exception('Invalid year. Must be YYYY');
77 | }
78 |
79 | $start = strtotime($year . '-01-01');
80 | $end = strtotime('+1 year', $start);
81 | return $this->where('created', '>=', $start)->where('created', '<', $end);
82 | }
83 |
84 | public function years() {
85 | return $this->library->database->query('select distinct strftime("%Y", created, "unixepoch") as year from items')->pluck('year');
86 | }
87 |
88 | public function months() {
89 | return $this->library->database->query('select distinct strftime("%Y-%m", created, "unixepoch") as month from items')->pluck('month');
90 | }
91 |
92 | public function days() {
93 | return $this->library->database->query('select distinct strftime("%Y-%m-%d", created, "unixepoch") as day from items')->pluck('day');
94 | }
95 |
96 | public function status($status) {
97 | return $this->where('status', '=', $status);
98 | }
99 |
100 | public function delete($where = null) {
101 | foreach($this->all() as $item) {
102 | $item->delete();
103 | }
104 | }
105 |
106 | public function search($q, $columns = array()) {
107 |
108 | $query = $this;
109 |
110 | if(empty($columns)) {
111 | $columns = $this->library->columns()->pluck('name');
112 | $columns = array_diff($columns, Item::$required);
113 | }
114 |
115 | $query->where(function($where) use($q, $columns) {
116 | foreach($columns as $column) {
117 | $where->orWhere($column, 'LIKE', '%' . $q . '%');
118 | }
119 | return $where;
120 | });
121 |
122 | return $query;
123 |
124 | }
125 |
126 | }
--------------------------------------------------------------------------------
/library/library.php:
--------------------------------------------------------------------------------
1 | root = $root;
16 |
17 | if(!$this->isWritable()) {
18 | throw new Exception('The library is not writable');
19 | }
20 |
21 | $this->index();
22 |
23 | }
24 |
25 | public function root() {
26 | return $this->root;
27 | }
28 |
29 | public function columns() {
30 | return $this->database->query('PRAGMA table_info(items)');
31 | }
32 |
33 | public function folder() {
34 | return new Folder($this->root);
35 | }
36 |
37 | public function isWritable() {
38 | return is_writable($this->root);
39 | }
40 |
41 | public function index() {
42 |
43 | if(!is_null($this->index)) return $this->index;
44 |
45 | $file = $this->root . DS . 'library.sqlite';
46 | $setup = !file_exists($file);
47 |
48 | $this->database = new Database(array(
49 | 'database' => $file,
50 | 'type' => 'sqlite'
51 | ));
52 |
53 | if($setup) {
54 | $this->setup();
55 | }
56 |
57 | return $this->index = new Query($this);
58 |
59 | }
60 |
61 | public function database() {
62 | return $this->database;
63 | }
64 |
65 | public function id() {
66 |
67 | $id = str::random(32);
68 |
69 | while($this->index()->where('id', '=', $id)->one()) {
70 | $id = str::random(32);
71 | }
72 |
73 | return $id;
74 |
75 | }
76 |
77 | public function create($type, $data = array()) {
78 | return Item::create($this, $type, $data);
79 | }
80 |
81 | public function item($data) {
82 | return new Item($this, $data['type'], $data);
83 | }
84 |
85 | public function delete($id) {
86 | if($item = $this->find($id)) {
87 | return $item->delete();
88 | } else {
89 | throw new Exception('The item could not be found');
90 | }
91 | }
92 |
93 | public function __call($method, $args) {
94 |
95 | // allowed query calls
96 | $queries = array(
97 | 'status',
98 | 'type',
99 | 'find',
100 | 'first',
101 | 'all',
102 | 'page',
103 | 'search',
104 | 'where',
105 | 'count',
106 | 'year',
107 | 'month',
108 | 'day',
109 | 'years',
110 | 'months',
111 | 'days',
112 | 'order'
113 | );
114 |
115 | if(in_array($method, $queries)) {
116 | return call(array($this->index, $method), $args);
117 | } else {
118 | throw new Exception('Invalid library method');
119 | }
120 |
121 | }
122 |
123 | public function setup() {
124 |
125 | $this->database->createTable('items', array(
126 | 'id' => array(
127 | 'type' => 'text',
128 | 'key' => 'unique',
129 | ),
130 | 'type' => array(
131 | 'type' => 'text',
132 | 'key' => 'index',
133 | ),
134 | 'status' => array(
135 | 'type' => 'text',
136 | 'key' => 'index',
137 | ),
138 | 'created' => array(
139 | 'type' => 'timestamp',
140 | 'key' => 'index',
141 | ),
142 | 'updated' => array(
143 | 'type' => 'timestamp'
144 | )
145 | ));
146 |
147 | $this->rebuild();
148 |
149 | }
150 |
151 | public function rebuild() {
152 |
153 | foreach($this->folder()->children() as $year) {
154 | foreach($year->children() as $month) {
155 | foreach($month->children() as $day) {
156 | foreach($day->children() as $item) {
157 | $data = data::read($item->root() . DS . 'item.yaml');
158 | $item = new Item($this, $data['type'], $data);
159 | $item->store();
160 | }
161 | }
162 | }
163 | }
164 |
165 | }
166 |
167 | public function clean($root) {
168 |
169 | if(!is_dir($root)) {
170 | throw new Exception('The given directory does not exist');
171 | }
172 |
173 | if(!str::startsWith($root, $this->root)) {
174 | throw new Exception('Invalid directory. Must be within the library');
175 | }
176 |
177 | while($root != $this->root) {
178 | $files = dir::read($root);
179 | if(count($files) === 0) {
180 | dir::remove($root);
181 | } else {
182 | break;
183 | }
184 | $root = dirname($root);
185 | }
186 |
187 | }
188 |
189 | }
190 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Library
2 |
3 | *The Library is a file-based, searchable data and file storage solution written in PHP.*
4 |
5 | It's easy to setup and use. It's intended for cheap shared hosting, small virtual servers, your NAS or RaspberryPi and similar thingies where a node.js or Rails based solution is too much or simply not installable.
6 |
7 | It can handle a couple thousand entries without any problems. It's perfect for small personal single-user applications: a self-hosted photo album, a tweet backup, a personal checkins db, an address book, a personal invoice app, a book inventory, a todo app — you name it.
8 |
9 | The Library is also a perfect combination of any kind of text-based data combined with attached files. Each stored item can have any number of additional images, documents, videos, etc. attached.
10 |
11 | The Library has no schema. You can add any number of fields per item and individual items can have different sets of fields.
12 |
13 | ***
14 |
15 | ## Table of contents
16 |
17 | - [Philosophy](#philosophy)
18 | - [Folder structure](#folder-structure)
19 | - [Sqlite index](#sqlite-index)
20 | - [Requirements](#requirements)
21 | - [Installation](#installation)
22 | - [Getting started](#getting-started)
23 | - [Creating items](#creating-items)
24 | - [Item anatomy](#item-anatomy)
25 | - [Item getters](#item-getters)
26 | - [Additional item methods](#additional-item-methods)
27 | - [Updates](#updates)
28 | - [Queries](#queries)
29 | - [Attachments](#attachments)
30 | - [Deleting items](#deleting-items)
31 | - [Roadmap](#roadmap)
32 | - [License](#license)
33 | - [Author](#author)
34 |
35 | ***
36 |
37 | ## Philosophy
38 |
39 | - practical
40 | - simple
41 | - robust
42 | - low fancy-level
43 | - high usefulness-level
44 | - for everyone
45 |
46 | ***
47 |
48 | ## Folder structure
49 |
50 | The Library stores each item in its own folder. The folder structure follows the creation date of the item:
51 |
52 | ```
53 | - library
54 | -- 2015
55 | --- 06
56 | ---- 10
57 | ----- 32-char-item-id
58 | ------ item.yaml
59 | ------ attachment-1.jpg
60 | ------ attachment-2.pdf
61 | ------ attachment-3.zip
62 | etc.
63 | ```
64 |
65 | You can easily have multiple libraries per app and user in different folders.
66 |
67 | ***
68 |
69 | ## SQLite Index
70 |
71 | The Library uses a simple SQLite database as a searchable index and builds on the database class of the Kirby toolkit to provide a nice and clean query api.
72 |
73 | The index is stored in the main directory of the library and can be removed at any time. The Library will take care of rebuilding the index once it's gone. The folder structure, attachments and yaml files are always the original data source, which makes this solution very robust.
74 |
75 | ***
76 |
77 | ## Requirements
78 |
79 | - PHP 5.4+
80 | - sqlite
81 | - mbstring
82 |
83 | ***
84 |
85 | ## Installation
86 |
87 | ```
88 | git clone https://github.com/bastianallgeier/library.git
89 | ```
90 |
91 | ***
92 |
93 | ## Getting started
94 |
95 | ```php
96 | require('library/bootstrap.php');
97 |
98 | $library = new Library(__DIR__ . '/mylibrary');
99 | ```
100 |
101 | Make sure the library folder is writable. Otherwise the library will not be able to store any data for you.
102 |
103 | ***
104 |
105 | ## Creating items
106 |
107 | ```php
108 | $item = $library->create('article', array(
109 | 'title' => 'Hello World',
110 | 'text' => 'Lorem ipsum…'
111 | ));
112 | ```
113 |
114 | ***
115 |
116 | ## Item anatomy
117 |
118 | Each item is stored in its own folder. The path to the folder follows the format YYYY/MM/DD/ID. The creation date is used herefor. Each item has the following default fields:
119 |
120 | ```
121 | - id (unique 32 char alphanumeric string)
122 | - status (draft|public|private)
123 | - type (can be any alphanumeric string between 2 and 32 characters)
124 | - created (unix timestamp)
125 | - updated (unix timestamp)
126 | ```
127 |
128 | Additionally you can add any number of fields to an item.
129 |
130 | ***
131 |
132 | ## Item getters
133 |
134 | ```php
135 | // standard getters
136 | $item->id();
137 | $item->status();
138 | $item->type();
139 | $item->created();
140 | $item->updated();
141 |
142 | // magic getters for your additional fields
143 | $item->title();
144 | $item->text();
145 | // etc.
146 | ```
147 |
148 | ***
149 |
150 | ## Additional item methods
151 |
152 | ```php
153 | // i.e. 2012/12/12/5gWsOULCwbkInoVQbJ0MxcjybX5hfkv9
154 | $item->path();
155 | // i.e. 2012
156 | $item->path('year');
157 | // i.e. 2012/12
158 | $item->path('month');
159 | // i.e. 2012/12/12
160 | $item->path('day');
161 |
162 | // i.e. /var/www/library/2012/12/12/5gWsOULCwbkInoVQbJ0MxcjybX5hfkv9
163 | $item->root();
164 | // i.e. /var/www/library/2012
165 | $item->root('year');
166 | // i.e. /var/www/library/2012/12
167 | $item->root('month');
168 | // i.e. /var/www/library/2012/12/12
169 | $item->root('day');
170 |
171 | // Kirby toolkit folder object
172 | // http://getkirby.com/docs/toolkit/api#folder
173 | $item->folder();
174 |
175 | // returns an associative array of all item fields
176 | $item->toArray();
177 |
178 | // checks if the item exists
179 | $item->exists();
180 | ```
181 | Check the following examples for more specific stuff.
182 |
183 | ***
184 |
185 | ## Updates
186 |
187 | ```php
188 | $item->update(array(
189 | 'title' => 'New title',
190 | 'text' => 'Lorem ipsum dolor sit amet',
191 | 'date' => time()
192 | ));
193 | ```
194 |
195 | ### Magic Setters
196 |
197 | ```php
198 | $item->title = 'New title';
199 | $item->text = 'Lorem ipsum…';
200 |
201 | $item->store();
202 | ```
203 |
204 | ### Set method
205 |
206 | ```php
207 | $item->set('title', 'New title');
208 | $item->set('text', 'Lorem ipsum…');
209 |
210 | $item->store();
211 | ```
212 |
213 | ### Setting multiple values
214 |
215 | ```php
216 | $item->set(array(
217 | 'title' => 'New title',
218 | 'text' => 'Lorem ipsum dolor sit amet'
219 | ));
220 |
221 | $item->store();
222 | ```
223 |
224 | ### Modifying the type
225 |
226 | ```php
227 | $item->type('blogpost');
228 | ```
229 |
230 | ### Modifying the creation date
231 |
232 | ```php
233 | $item->created('2012-12-12 22:33');
234 | ```
235 |
236 | ### Switching the status
237 |
238 | ```php
239 | $item->status('public');
240 | ```
241 |
242 | Available statuses: draft (default), public, private
243 |
244 | ***
245 |
246 | ## Queries
247 |
248 | ### Finding a single item by id
249 |
250 | ```php
251 | $item = $library->find('ekM9AZMIWbkm48hlpRCJO52FVCQSkClL');
252 | ```
253 |
254 | ### All items from the library
255 |
256 | ```php
257 | $items = $library->all();
258 | ```
259 |
260 | ### Pagination
261 |
262 | ```php
263 | $items = $library->page($page, $limit);
264 | ```
265 |
266 | ### Counting all items
267 |
268 | ```php
269 | $count = $library->count();
270 | ```
271 |
272 | ### Filtering the library by type
273 |
274 | ```php
275 | $items = $library->type('article')->all();
276 | $items = $library->type('article')->page($page, $limit);
277 | $count = $library->type('article')->count();
278 | ```
279 |
280 | ### Filtering the library by status
281 |
282 | ```php
283 | $items = $library->status('draft')->all();
284 | $items = $library->status('public')->page($page, $limit);
285 | $count = $library->status('private')->count();
286 | ```
287 |
288 | ### Filtering the library by year
289 |
290 | ```php
291 | $items = $library->year('2015')->all();
292 | $items = $library->year('2015')->page($page, $limit);
293 | $count = $library->year('2015')->count();
294 | ```
295 |
296 | ### Filtering the library by month
297 |
298 | ```php
299 | $items = $library->month('2015-06')->all();
300 | $items = $library->month('2015-06')->page($page, $limit);
301 | $count = $library->month('2015-06')->count();
302 | ```
303 |
304 | ### Filtering the library by day
305 |
306 | ```php
307 | $items = $library->day('2015-06-10')->all();
308 | $items = $library->day('2015-06-10')->page($page, $limit);
309 | $count = $library->day('2015-06-10')->count();
310 | ```
311 |
312 | ### Searching the library
313 |
314 | ```php
315 | $items = $library->search($query)->all();
316 | $items = $library->search($query)->page($page, $limit);
317 | $count = $library->search($query)->count();
318 | ```
319 |
320 | ### Combining filters
321 |
322 | ```php
323 | $items = $library->year('2015')->type('article')->status('public')->search($query)->all();
324 | $items = $library->year('2015')->type('article')->status('public')->search($query)->page($page, $limit);
325 | $count = $library->year('2015')->type('article')->status('public')->search($query)->count();
326 | ```
327 |
328 | ***
329 |
330 | ## Attachments
331 |
332 | ### Attaching a file via URL
333 |
334 | ```php
335 | $item->attach('http://example.com/image.jpg');
336 | ```
337 |
338 | ### Attaching a file from the file system
339 |
340 | ```php
341 | $item->attach('/some/path/image.jpg');
342 | ```
343 |
344 | ### Setting a custom filename
345 |
346 | ```php
347 | $item->attach('/some/path/image.jpg', 'myimage.jpg');
348 | ```
349 |
350 | ### Fetching attachments for an item
351 |
352 | ```php
353 | // all files
354 | $files = $item->files();
355 |
356 | // all images
357 | $images = $item->images();
358 |
359 | // all videos
360 | $videos = $item->videos();
361 |
362 | // all documents
363 | $documents = $item->documents();
364 | ```
365 |
366 | All methods above return a Kirby Toolkit Collection of Media objects:
367 | -
368 | -
369 |
370 | ### Deleting an attachment
371 |
372 | ```php
373 | $item->detach('myimage.jpg);
374 | ```
375 |
376 | ***
377 |
378 | ## Deleting items
379 |
380 | ### Deleting a single item
381 |
382 | ```php
383 | $item->delete();
384 | ```
385 |
386 | ## Deleting multiple items from the library
387 |
388 | ```php
389 | $library->type('article')->delete();
390 | ```
391 |
392 | ***
393 |
394 | ## Roadmap
395 |
396 | - more docs
397 | - "between" query method for dates
398 | - maybe geo queries
399 | - Travis setup
400 | - Composer support
401 |
402 | ***
403 |
404 | ## License
405 |
406 |
407 |
408 | ***
409 |
410 | ## Author
411 |
412 | Bastian Allgeier
413 | -
414 | -
415 | -
416 |
--------------------------------------------------------------------------------
/library/item.php:
--------------------------------------------------------------------------------
1 | library = $library;
29 |
30 | // set the type
31 | $this->type = $type;
32 |
33 | // add all fields
34 | $this->set($data);
35 |
36 | // auto-create an id for new items
37 | if(!isset($this->data['id'])) {
38 | $this->id = $library->id();
39 | }
40 |
41 | // auto-set the status to draft if nothing is set
42 | if(!isset($this->data['status'])) {
43 | $this->status = 'draft';
44 | }
45 |
46 | // auto-set the updated timestamp
47 | if(!isset($this->data['updated'])) {
48 | $this->updated = time();
49 | }
50 |
51 | // auto-set the created timestamp
52 | if(!isset($this->data['created'])) {
53 | $this->created = time();
54 | }
55 |
56 | // validate the object
57 | $this->validate();
58 |
59 | }
60 |
61 | public function validate() {
62 |
63 | // check for a valid library object
64 | if(!is_a($this->library, 'Library')) {
65 | throw new Exception('The library object is invalid');
66 | }
67 |
68 | // check for all required fields
69 | foreach(static::$required as $field) {
70 | if(empty($this->data[$field])) {
71 | throw new Exception('Missing required field: ' . $field);
72 | }
73 | }
74 |
75 | // id validation
76 | if(!is_string($this->data['id']) or !v::alphanum($this->data['id']) or str::length($this->data['id']) !== 32) {
77 | throw new Exception('Invalid id');
78 | }
79 |
80 | // type validation
81 | if(!is_string($this->data['type']) or !v::between($this->data['type'], 2, 32)) {
82 | throw new Exception('Invalid type');
83 | }
84 |
85 | // status validation
86 | if(!in_array($this->data['status'], static::$statuses)) {
87 | throw new Exception('Invalid status: ' . $this->data['status']);
88 | }
89 |
90 | // check for invalid updated timestamp
91 | if(!is_int($this->data['updated']) or !v::between(date('Y', $this->data['updated']), 1980, 2500)) {
92 | throw new Exception('Invalid updated timestamp');
93 | }
94 |
95 | // check for invalid created timestamp
96 | if(!is_int($this->data['created']) or !v::between(date('Y', $this->data['created']), 1980, 2500) or $this->data['created'] > time()) {
97 | throw new Exception('Invalid created timestamp');
98 | }
99 |
100 | }
101 |
102 | static public function create(Library $library, $type, $data = array()) {
103 | $item = new static($library, $type, $data);
104 | return $item->store();
105 | }
106 |
107 | public function __set($key, $value) {
108 |
109 | // convert nulls to empty strings
110 | if(is_null($value)) $value = '';
111 |
112 | // avoid invalid values
113 | if(!is_scalar($value)) {
114 | throw new Exception('Invalid value type');
115 | }
116 |
117 | // avoid invalid keys
118 | if(!is_string($key)) {
119 | throw new Exception('Invalid key type');
120 | }
121 |
122 | // sanitize the key
123 | $key = str_replace('-', '_', str::slug($key));
124 |
125 | // sanitize the timestamps
126 | if($key == 'updated' or $key == 'created') {
127 | $value = intval($value);
128 | }
129 |
130 | // clean string values
131 | if(is_string($value)) {
132 | $value = trim($value);
133 | }
134 |
135 | // store the last state
136 | if(isset($this->data[$key])) {
137 | $this->old[$key] = $this->$key;
138 | } else {
139 | $this->old[$key] = $value;
140 | }
141 |
142 | // store it
143 | $this->data[$key] = $value;
144 |
145 | }
146 |
147 | public function __get($key) {
148 | $key = str::slug($key);
149 | if(isset($this->data[$key])) {
150 | return $this->data[$key];
151 | } else {
152 | return null;
153 | }
154 | }
155 |
156 | public function get($key) {
157 | return $this->__get($key);
158 | }
159 |
160 | public function __call($key, $args) {
161 | return $this->__get($key);
162 | }
163 |
164 | public function set($key, $value = null) {
165 | if(is_array($key)) {
166 | foreach($key as $k => $v) {
167 | $this->__set($k, $v);
168 | }
169 | return $this;
170 | }
171 | $this->__set($key, $value);
172 | return $this;
173 | }
174 |
175 | public function status($status = null) {
176 |
177 | if(is_null($status)) {
178 | return $this->status;
179 | }
180 |
181 | $this->status = $status;
182 | $this->store();
183 |
184 | return $this;
185 |
186 | }
187 |
188 | public function type($type = null) {
189 |
190 | if(is_null($type)) {
191 | return $this->type;
192 | }
193 |
194 | $this->type = $type;
195 | $this->store();
196 |
197 | return $this;
198 |
199 | }
200 |
201 | public function created($created = null) {
202 |
203 | if(is_null($created)) {
204 | return $this->created;
205 | }
206 |
207 | // handles all parseable strings
208 | if(!is_int($created)) {
209 | $created = strtotime($created);
210 | }
211 |
212 | // store the new creation date and validate it
213 | $this->created = $created;
214 | $this->store();
215 |
216 | return $this;
217 |
218 | }
219 |
220 | public function update($data = array()) {
221 |
222 | $this->set($data);
223 | $this->set('updated', time());
224 | $this->store();
225 |
226 | return $this;
227 |
228 | }
229 |
230 | public function attach($file, $filename = null) {
231 |
232 | // if the item has not been stored yet
233 | // throw an exception
234 | if(!$this->exists()) {
235 | throw new Exception('Unstored item');
236 | }
237 |
238 | // filename fallback
239 | if(is_null($filename)) {
240 | $filename = basename($file);
241 | }
242 |
243 | // sanitize the filename
244 | $filename = f::safeName($filename);
245 |
246 | // the item.yaml cannot be overwritten
247 | if($filename == 'item.yaml') {
248 | throw new Exception('item.yaml is a reserved filename');
249 | }
250 |
251 | // files cannot be overwritten
252 | if(file_exists($this->root() . DS . $filename)) {
253 | throw new Exception('The file exists and cannot be overwritten');
254 | }
255 |
256 | // attach a remote url
257 | if(v::url($file)) {
258 | $response = remote::get($file);
259 | if($response->code() < 400) {
260 | if(!f::write($this->root() . DS . $filename, $response->content())) {
261 | throw new Exception('The file could not be saved');
262 | }
263 | } else {
264 | throw new Exception('The file could not be fetched');
265 | }
266 | } else if(file_exists($file)) {
267 | if(!f::copy($file, $this->root() . DS . $filename)) {
268 | throw new Exception('The file could not be copied');
269 | }
270 | }
271 |
272 | }
273 |
274 | public function detach($filename) {
275 |
276 | $filename = f::safeName($filename);
277 |
278 | if($filename == 'item.yaml') {
279 | throw new Exception('The item.yaml file cannot be removed');
280 | }
281 |
282 | if(!f::remove($this->root() . DS . $filename)) {
283 | throw new Exception('The file cannot be removed');
284 | }
285 |
286 | }
287 |
288 | public function delete() {
289 |
290 | // get this before the item is killed
291 | $dayroot = $this->root('day');
292 |
293 | if(!dir::remove($this->root())) {
294 | throw new Exception('The item directory could not be removed');
295 | }
296 |
297 | if(!$this->library->database->query('delete from items where id = :id', array('id' => $this->id))) {
298 | throw new Exception('The delete query failed');
299 | }
300 |
301 | // make sure to clean up the directory attic
302 | $this->library->clean($dayroot);
303 |
304 | }
305 |
306 | public function toArray() {
307 | return $this->data;
308 | }
309 |
310 | public function exists() {
311 | return is_dir($this->root());
312 | }
313 |
314 | public function store() {
315 |
316 | // make sure the item is valid before storing it
317 | $this->validate();
318 |
319 | // make sure the directory is at the right position
320 | $this->relocate();
321 |
322 | $index = $this->library->index();
323 | $data = $this->toArray();
324 | $file = $this->root() . DS . 'item.yaml';
325 | $columns = $this->library->columns()->pluck('name');
326 | $missing = array_diff(array_keys($data), $columns);
327 |
328 | // add additional columns
329 | foreach($missing as $column) {
330 | $this->library->database->query('alter table items add ' . $column . ' text');
331 | }
332 |
333 | // store the data in the file
334 | data::write($file, $data);
335 |
336 | // clean the index first
337 | $this->library->database->query('delete from items where id = :id', array('id' => $this->id));
338 |
339 | // and then re-add
340 | $index->insert($data);
341 |
342 | return $this;
343 |
344 | }
345 |
346 | public function folder() {
347 | return new Folder($this->root());
348 | }
349 |
350 | public function files() {
351 | return $this->folder()->files()->not('item.yaml');
352 | }
353 |
354 | public function images() {
355 | return $this->files()->filterBy('type', 'image');
356 | }
357 |
358 | public function videos() {
359 | return $this->files()->filterBy('type', 'video');
360 | }
361 |
362 | public function documents() {
363 | return $this->files()->filterBy('type', 'document');
364 | }
365 |
366 | public function path($type = null) {
367 |
368 | switch(strtolower($type)) {
369 | case 'year':
370 | return date('Y', $this->created);
371 | case 'month':
372 | return date('Y/m', $this->created);
373 | case 'day':
374 | return date('Y/m/d', $this->created);
375 | default:
376 | return date('Y/m/d', $this->created) . '/' . $this->id;
377 | }
378 |
379 | }
380 |
381 | public function root($type = null) {
382 | return $this->library->root . DS . str_replace('/', DS, $this->path($type));
383 | }
384 |
385 | protected function relocate() {
386 |
387 | if($this->old['created'] == $this->data['created']) return;
388 |
389 | $old = clone $this;
390 | $old->created = $this->old['created'];
391 |
392 | if(!$old->exists()) return;
393 |
394 | if(!dir::make($this->root())) {
395 | throw new Exception('The new directory could not be created');
396 | }
397 |
398 | if(!dir::move($old->root(), $this->root())) {
399 | throw new Exception('The directory could not be moved');
400 | }
401 |
402 | $this->library->clean($old->root('day'));
403 |
404 | }
405 |
406 | }
--------------------------------------------------------------------------------
/test/ItemTest.php:
--------------------------------------------------------------------------------
1 | library, null);
15 | }
16 |
17 | public function testSuccessfulItemCreation() {
18 | $item = new Item($this->library, 'todo');
19 | $this->assertInstanceOf('Library\\Item', $item);
20 | }
21 |
22 | /**
23 | * @expectedException Exception
24 | * @expectedExceptionMessage Invalid id
25 | */
26 | public function testCreateItemWithInvalidShortId() {
27 | $item = new Item($this->library, 'todo', array(
28 | 'id' => 'abc',
29 | ));
30 | }
31 |
32 | /**
33 | * @expectedException Exception
34 | * @expectedExceptionMessage Invalid id
35 | */
36 | public function testCreateItemWithInvalidCharInId() {
37 | $item = new Item($this->library, 'todo', array(
38 | 'id' => '#abGuMgoNV5HfbRZbJFCIbDHMXmATJ8d'
39 | ));
40 | }
41 |
42 |
43 | /**
44 | * @expectedException Exception
45 | * @expectedExceptionMessage Invalid type
46 | */
47 | public function testCreateItemWithInvalidCharsInType() {
48 |
49 | // not a string
50 | $item = new Item($this->library, 1);
51 |
52 | }
53 |
54 | /**
55 | * @expectedException Exception
56 | * @expectedExceptionMessage Invalid type
57 | */
58 | public function testCreateItemWithInvalidShortType() {
59 | $item = new Item($this->library, 't');
60 | }
61 |
62 | /**
63 | * @expectedException Exception
64 | * @expectedExceptionMessage Invalid type
65 | */
66 | public function testCreateItemWithInvalidLongType() {
67 | $item = new Item($this->library, 'taskdasdhkajhsdkjahskjdhakjsdhkjahsdkjahsdk');
68 | }
69 |
70 | /**
71 | * @expectedException Exception
72 | * @expectedExceptionMessage Invalid status
73 | */
74 | public function testCreateItemWithInvalidStatus() {
75 | $item = new Item($this->library, 'todo', array(
76 | 'status' => 'abc'
77 | ));
78 | }
79 |
80 | /**
81 | * @expectedException Exception
82 | * @expectedExceptionMessage Invalid updated timestamp
83 | */
84 | public function testCreateItemWithInvalidUpdatedTimestamp() {
85 |
86 | $item = new Item($this->library, 'todo', array(
87 | 'updated' => strtotime('1900-01-01')
88 | ));
89 |
90 | $item = new Item($this->library, 'todo', array(
91 | 'updated' => strtotime('2900-01-01')
92 | ));
93 |
94 | }
95 |
96 | /**
97 | * @expectedException Exception
98 | * @expectedExceptionMessage Invalid created timestamp
99 | */
100 | public function testCreateItemWithInvalidCreatedTimestamp() {
101 |
102 | $item = new Item($this->library, 'todo', array(
103 | 'created' => strtotime('1900-01-01')
104 | ));
105 |
106 | $item = new Item($this->library, 'todo', array(
107 | 'created' => strtotime('2900-01-01')
108 | ));
109 |
110 | $item = new Item($this->library, 'todo', array(
111 | 'created' => time() + 10
112 | ));
113 |
114 | }
115 |
116 | public function testCreate() {
117 |
118 | $item = Item::create($this->library, 'todo');
119 |
120 | $this->assertEquals('todo', $item->type());
121 | $this->assertEquals('draft', $item->status());
122 |
123 | // search for the item in the library
124 | $this->assertEquals($item, $this->library->find($item->id));
125 |
126 | }
127 |
128 | /**
129 | * @expectedException Exception
130 | * @expectedExceptionMessage Invalid value type
131 | */
132 | public function testSetWithInvalidArrayValue() {
133 |
134 | $item = $this->item();
135 | $item->tags = array('a','b','c');
136 |
137 | }
138 |
139 | /**
140 | * @expectedException Exception
141 | * @expectedExceptionMessage Invalid value type
142 | */
143 | public function testSetWithInvalidObjectValue() {
144 |
145 | $item = $this->item();
146 | $item->tags = new stdClass;
147 |
148 | }
149 |
150 | /**
151 | * @expectedException Exception
152 | * @expectedExceptionMessage Invalid value type
153 | */
154 | public function testSetWithInvalidResourceValue() {
155 |
156 | $item = $this->item();
157 | $item->tags = tmpfile();
158 |
159 | }
160 |
161 | /**
162 | * @expectedException Exception
163 | * @expectedExceptionMessage Invalid key type
164 | */
165 | public function testSetWithInvalidKey() {
166 |
167 | $item = $this->item();
168 | $item->set(1, 'test');
169 |
170 | }
171 |
172 | public function testMultipleSet() {
173 |
174 | $item = $this->item();
175 | $item->set(array(
176 | 'keyA' => 'valueA',
177 | 'keyB' => 'valueB'
178 | ));
179 |
180 | $this->assertEquals('valueA', $item->keyA);
181 | $this->assertEquals('valueB', $item->keyB);
182 |
183 | }
184 |
185 | public function testSet() {
186 |
187 | // Version A
188 | $item = $this->item();
189 | $item->title = 'test';
190 |
191 | $this->assertEquals('test', $item->title);
192 |
193 | // Version B
194 | $item = $this->item();
195 | $item->set('title', 'test');
196 |
197 | $this->assertEquals('test', $item->title);
198 |
199 | // Version C
200 | $item = $this->item();
201 | $item->__set('title', 'test');
202 |
203 | $this->assertEquals('test', $item->title);
204 |
205 | }
206 |
207 | public function testGet() {
208 |
209 | $item = $this->item();
210 | $item->title = 'test';
211 |
212 | $this->assertEquals('test', $item->title);
213 | $this->assertEquals('test', $item->title());
214 | $this->assertEquals('test', $item->get('title'));
215 | $this->assertEquals('test', $item->__get('title'));
216 |
217 | // different key versions
218 | $this->assertEquals('test', $item->Title);
219 | $this->assertEquals('test', $item->TITLE);
220 | $this->assertEquals('test', $item->tItLe);
221 |
222 | }
223 |
224 | public function testPath() {
225 |
226 | $item = $this->item();
227 |
228 | $this->assertEquals(date('Y'), $item->path('year'));
229 | $this->assertEquals(date('Y/m'), $item->path('month'));
230 | $this->assertEquals(date('Y/m/d'), $item->path('day'));
231 | $this->assertEquals(date('Y/m/d') . DS . $item->id, $item->path());
232 |
233 | }
234 |
235 | public function testRoot() {
236 |
237 | $item = $this->item();
238 |
239 | $this->assertEquals($this->root . DS . date('Y'), $item->root('year'));
240 | $this->assertEquals($this->root . DS . date('Y/m'), $item->root('month'));
241 | $this->assertEquals($this->root . DS . date('Y/m/d'), $item->root('day'));
242 | $this->assertEquals($this->root . DS . date('Y/m/d') . DS . $item->id, $item->root());
243 |
244 | }
245 |
246 | public function testStatus() {
247 |
248 | $item = $this->item();
249 |
250 | $this->assertEquals('draft', $item->status);
251 |
252 | $item->status('public');
253 |
254 | $this->assertEquals('public', $item->status);
255 |
256 | $item->status('private');
257 |
258 | $this->assertEquals('private', $item->status);
259 |
260 | $item->status('draft');
261 |
262 | $this->assertEquals('draft', $item->status);
263 |
264 | }
265 |
266 | public function testType() {
267 |
268 | $item = $this->item();
269 |
270 | $this->assertEquals('test', $item->type);
271 |
272 | $item->type('sometype');
273 |
274 | $this->assertEquals('sometype', $item->type);
275 |
276 | }
277 |
278 | public function testCreated() {
279 |
280 | $item = $this->item();
281 |
282 | $this->assertEquals(time(), $item->created);
283 |
284 | $item->created('2012-12-12 22:33');
285 |
286 | $this->assertEquals(strtotime('2012-12-12 22:33'), $item->created);
287 |
288 | }
289 |
290 | public function testUpdate() {
291 |
292 | $item = $this->item();
293 | $item->title = 'A';
294 | $item->updated = time() - 10;
295 | $item->store();
296 |
297 | $updated = $item->updated();
298 |
299 | $this->assertEquals('A', $item->title);
300 | $this->assertEquals(1, $this->library->index->where('title', '=', 'A')->count());
301 | $this->assertEquals(0, $this->library->index->where('title', '=', 'B')->count());
302 |
303 | $item->update(array(
304 | 'title' => 'B'
305 | ));
306 |
307 | $this->assertEquals('B', $item->title);
308 | $this->assertEquals(0, $this->library->index->where('title', '=', 'A')->count());
309 | $this->assertEquals(1, $this->library->index->where('title', '=', 'B')->count());
310 |
311 | $this->assertTrue($updated < $item->updated());
312 |
313 | }
314 |
315 | public function testDelete() {
316 |
317 | $item = $this->item();
318 | $root = $item->root();
319 | $id = $item->id();
320 |
321 | $item->store();
322 |
323 | $this->assertTrue(is_dir($root));
324 | $this->assertEquals(1, $this->library->index->where('id', '=', $id)->count());
325 |
326 | $item->delete();
327 |
328 | // check for proper cleaning of directories
329 | $this->assertFalse(is_dir($root));
330 | $this->assertFalse(is_dir(dirname($root)));
331 | $this->assertFalse(is_dir(dirname(dirname($root))));
332 | $this->assertFalse(is_dir(dirname(dirname(dirname($root)))));
333 |
334 | // check for a clean index table
335 | $this->assertEquals(0, $this->library->index->where('id', '=', $id)->count());
336 |
337 | }
338 |
339 | public function testToArray() {
340 |
341 | $item = $this->item();
342 |
343 | $item->type = 'test';
344 | $item->status = 'public';
345 | $item->title = 'test';
346 |
347 | $item->store();
348 |
349 | $result = array(
350 | 'type' => 'test',
351 | 'id' => $item->id,
352 | 'status' => 'public',
353 | 'updated' => time(),
354 | 'created' => time(),
355 | 'title' => 'test'
356 | );
357 |
358 | $this->assertEquals($result, $item->toArray());
359 |
360 | }
361 |
362 | /**
363 | * @expectedException Exception
364 | * @expectedExceptionMessage Unstored item
365 | */
366 | public function testAttachWithUnstoredItem() {
367 |
368 | $item = $this->item();
369 | $item->attach('http://domain.com/image.jpg');
370 |
371 | }
372 |
373 | /**
374 | * @expectedException Exception
375 | * @expectedExceptionMessage The file could not be fetched
376 | */
377 | public function testAttachWithBrokenLink() {
378 |
379 | $item = $this->item();
380 | $item->store();
381 | $item->attach('http://getkirby.com/404.jpg');
382 |
383 | }
384 |
385 | /**
386 | * @expectedException Exception
387 | * @expectedExceptionMessage item.yaml is a reserved filename
388 | */
389 | public function testAttachItemYaml() {
390 |
391 | $item = $this->item();
392 | $item->store();
393 | $item->attach(__FILE__, 'item.yaml');
394 |
395 | }
396 |
397 | /**
398 | * @expectedException Exception
399 | * @expectedExceptionMessage The file exists and cannot be overwritten
400 | */
401 | public function testAttachOverwritingFiles() {
402 |
403 | $item = $this->item();
404 | $item->store();
405 | $item->attach(__FILE__, 'test.php');
406 | $item->attach(__FILE__, 'test.php');
407 |
408 | }
409 |
410 | public function testAttachFromUrl() {
411 |
412 | $item = $this->item();
413 | $item->store();
414 | $item->attach('http://getkirby.com/assets/images/logo.png');
415 |
416 | $this->assertTrue(file_exists($item->root() . DS . 'logo.png'));
417 | $this->assertEquals('logo.png', $item->images()->first()->filename());
418 | $this->assertEquals('image/png', $item->images()->first()->mime());
419 |
420 | }
421 |
422 | public function testAttachFromUrlWithCustomFilename() {
423 |
424 | $item = $this->item();
425 | $item->store();
426 | $item->attach('http://getkirby.com/assets/images/logo.png', 'myfile.png');
427 |
428 | $this->assertTrue(file_exists($item->root() . DS . 'myfile.png'));
429 | $this->assertEquals('myfile.png', $item->images()->first()->filename());
430 | $this->assertEquals('image/png', $item->images()->first()->mime());
431 |
432 | }
433 |
434 | public function testAttachFromFile() {
435 |
436 | $item = $this->item();
437 | $item->store();
438 | $item->attach(__FILE__);
439 |
440 | $filename = f::safeName(basename(__FILE__));
441 |
442 | $this->assertTrue(file_exists($item->root() . DS . $filename));
443 | $this->assertEquals($filename, $item->files()->first()->filename());
444 |
445 | }
446 |
447 | public function testAttachFromFileWithCustomFilename() {
448 |
449 | $item = $this->item();
450 | $item->store();
451 | $item->attach(__FILE__, 'myfile.php');
452 |
453 | $this->assertTrue(file_exists($item->root() . DS . 'myfile.php'));
454 | $this->assertEquals('myfile.php', $item->files()->first()->filename());
455 |
456 | }
457 |
458 | public function testDetach() {
459 |
460 | $item = $this->item();
461 | $item->store();
462 |
463 | $item->attach(__FILE__, 'myfile.php');
464 |
465 | $this->assertTrue(file_exists($item->root() . DS . 'myfile.php'));
466 |
467 | $item->detach('myfile.php');
468 |
469 | $this->assertFalse(file_exists($item->root() . DS . 'myfile.php'));
470 |
471 | }
472 |
473 | /**
474 | * @expectedException Exception
475 | * @expectedExceptionMessage The file cannot be removed
476 | */
477 | public function testDetachInvalidFile() {
478 |
479 | $item = $this->item();
480 |
481 | $item->store();
482 |
483 | $item->detach('myfile.php');
484 |
485 | }
486 |
487 | /**
488 | * @expectedException Exception
489 | * @expectedExceptionMessage The item.yaml file cannot be removed
490 | */
491 | public function testDetachItemYaml() {
492 |
493 | $item = $this->item();
494 |
495 | $item->store();
496 |
497 | $item->detach('item.yaml');
498 |
499 | }
500 |
501 |
502 | public function testExists() {
503 |
504 | $item = $this->item();
505 |
506 | $this->assertFalse($item->exists());
507 |
508 | $item->store();
509 |
510 | $this->assertTrue($item->exists());
511 |
512 | }
513 |
514 | public function testRelocate() {
515 |
516 | $item = $this->item();
517 | $root = $item->root();
518 |
519 | $item->store();
520 |
521 | $this->assertTrue(is_dir($root));
522 |
523 | $item->created('2012-12-12 22:33');
524 |
525 | // check for proper cleaning of directories
526 | $this->assertFalse(is_dir($root));
527 | $this->assertFalse(is_dir(dirname($root)));
528 | $this->assertFalse(is_dir(dirname(dirname($root))));
529 | $this->assertFalse(is_dir(dirname(dirname(dirname($root)))));
530 |
531 | // check for a new root
532 | $this->assertTrue(is_dir($item->root()));
533 |
534 | }
535 |
536 | public function testColumnAddition() {
537 |
538 | $item = $this->item();
539 | $columns = $this->library->columns()->pluck('name');
540 | $expected = array(
541 | 'id',
542 | 'type',
543 | 'status',
544 | 'created',
545 | 'updated'
546 | );
547 |
548 | $this->assertEquals($expected, $columns);
549 |
550 | $item->update(array(
551 | 'title' => 'test'
552 | ));
553 |
554 | // check the list of columns again
555 | $columns = $this->library->columns()->pluck('name');
556 |
557 | // the list of columns should now also have the title
558 | $expected[] = 'title';
559 |
560 | $this->assertEquals($expected, $columns);
561 |
562 | }
563 |
564 | }
--------------------------------------------------------------------------------