1 /* Copyright (C) 2000-2004 Thomas Bopp, Thorsten Hampel, Ludger Merkens
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * $Id: Container.pike,v 1.5 2009/05/06 19:23:10 astra Exp $
19 inherit "/classes/Object";
20 #include <attributes.h>
27 //! A Container is an object that holds other objects (no users).
28 class Container : public Object{
35 private array oaInventory; // the containers inventory
48 add_data_storage(STORE_CONTAINER, store_container, restore_container);
54 * This function is called by delete to delete this object.
62 array inventory = copy_value(oaInventory);
64 foreach( inventory, object inv ) {
65 // dont delete Users !
69 if ( inv->get_object_class() & CLASS_USER) {
71 inv->move(inv->query_attribute(USER_WORKROOM));
86 * Duplicate an object - that is create a copy, the permisions are
89 * @param recursive - should the container be copied recursively?
90 * @return the copy of this object
93 object duplicate(void|bool recursive)
95 return ::duplicate(([ "recursive": recursive, ]));
98 mapping copy(object obj, mapping vars)
100 mapping copies = ::copy(obj, vars);
101 if ( mappingp(vars) && vars->recursive ) {
102 foreach( obj->get_inventory(), object inv ) {
105 if ( inv->get_object_class() & CLASS_USER)
110 copied = inv->do_duplicate(vars);
112 new_inv = copied[inv];
115 FATAL("Duplication of #" + inv->get_object_id());
116 FATAL("Error while duplicating recursively !\n"+
117 sprintf("%O", err[0])+"\n"+sprintf("%O",err[1]));
127 foreach(oaInventory, object inv) {
128 if ( objectp(inv) ) {
129 mixed err = catch(inv->update_path());
131 FATAL("Error while updating path: %O\n%O", err[0], err[1]);
138 * Check if it is possible to insert the object here.
140 * @param object obj - the object to insert
141 * @return true or false
145 bool check_insert(object obj)
147 if ( obj->get_object_class() & CLASS_ROOM )
148 steam_user_error("Unable to insert a Room-Object into Container !");
150 if ( obj->get_object_class() & CLASS_USER )
151 steam_user_error("Unable to insert User into Container !");
153 if ( obj->get_object_class() & CLASS_EXIT )
154 steam_user_error("Unable to insert Exits into Container !");
161 void check_environment(object env, object obj)
167 steam_error("Recursion detected in environment!");
168 env = env->get_environment();
169 check_environment(env, obj);
173 * Insert an object in the containers inventory. This is called by
174 * the move function - don't call this function myself.
176 * @param obj - the object to insert into the container
177 * @return true if object was inserted
181 insert_obj(object obj)
183 ASSERTINFO(IS_PROXY(obj), "Object is not a proxy");
188 if ( CALLER != obj->get_object() ) // only insert proxy objects
189 steam_error("Container.insert_obj() failed to insert non-proxy !");
190 if ( !arrayp(oaInventory) )
193 check_insert(obj); // does throw
195 steam_error("Cannot insert object into itself !");
196 check_environment(get_environment(), obj);
198 if ( search(oaInventory, obj) != -1 ) {
199 FATAL("Inserting object %O twice into %O! ----\n%s---\n", obj,
203 try_event(EVENT_ENTER_INVENTORY, obj);
205 // do not set, if user is added to inventory
206 if ( !(obj->get_object_class() & CLASS_USER) ) {
207 do_set_attribute(CONT_USER_MODIFIED, this_user());
208 do_set_attribute(CONT_LAST_MODIFIED, time());
210 oaInventory += ({ obj });
212 require_save(STORE_CONTAINER, UNDEFINED, SAVE_INSERT, ({ obj }));
214 run_event(EVENT_ENTER_INVENTORY, obj);
221 * Remove an object from the container. This function can only be
222 * called by the object itself and should only be called by the move function.
224 * @param obj - the object to insert into the container
225 * @return true if object was removed
228 bool remove_obj(object obj)
230 if ( !objectp(obj) ||
233 ASSERTINFO(arrayp(oaInventory), "Inventory not initialized!");
234 try_event(EVENT_LEAVE_INVENTORY, obj);
236 do_set_attribute(CONT_LAST_MODIFIED, time());
237 oaInventory -= ({ obj });
239 require_save(STORE_CONTAINER, UNDEFINED, SAVE_REMOVE, ({ obj }));
240 run_event(EVENT_LEAVE_INVENTORY, obj);
245 * Get the inventory of this container.
247 * @param void|int from_obj - the starting object
248 * @param void|int to_obj - the end of an object range.
249 * @return a list of objects contained by this container
251 * @see get_inventory_by_class
253 array get_inventory(int|void from_obj, int|void to_obj)
255 oaInventory -= ({0});
256 try_event(EVENT_GET_INVENTORY, CALLER);
257 run_event(EVENT_GET_INVENTORY, CALLER);
260 return oaInventory[from_obj..to_obj];
261 else if ( from_obj > 0 )
262 return oaInventory[from_obj..];
263 return copy_value(oaInventory);
267 * Get the content of this container - only relevant for multi
268 * language containers.
270 * @return content of index file
272 string get_content(void|string language)
274 if ( do_query_attribute("cont_type") == "multi_language" ) {
275 mapping index = do_query_attribute("language_index");
276 if ( objectp(index[language]) )
277 return index[language]->get_content();
278 if ( objectp(index->default) )
279 return index["default"]->get_content();
286 * Returns the inventory of this container, optionally filtered by object
287 * class, attribute values or pagination.
288 * The description of the filters and sort options can be found in the
289 * filter_objects_array() function of the "searching" module.
292 * Return all documents with keywords "urgent" or "important" that the user
293 * has read access to, that are no wikis and that have been changed in the
294 * last 24 hours, sort them by modification date (newest first) and return
295 * only the first 10 results:
296 * get_inventory_filtered(
298 * ({ "-", "!access", SANCTION_READ }),
299 * ({ "-", "attribute", "OBJ_TYPE", "prefix", "container_wiki" }),
300 * ({ "-", "attribute", "DOC_LAST_MODIFIED", "<", time()-86400 }),
301 * ({ "-", "attribute", "OBJ_KEYWORDS", "!=", ({ "urgent", "important" }) }),
302 * ({ "+", "class", CLASS_DOCUMENT })
305 * ({ ">", "attribute", "DOC_LAST_MODIFIED" })
308 * @param filters (optional) an array of filters (each an array as described
309 * in the "searching" module) that specify which objects to return
310 * @param sort (optional) an array of sort entries (each an array as described
311 * in the "searching" module) that specify the order of the items
312 * @param offset (optional) only return the objects starting at (and including)
314 * @param length (optional) only return a maximum of this many objects
315 * @return a mapping ([ "objects":({...}), "total":nr, "length":nr,
316 * "start":nr, "page":nr ]), where the "objects" value is an array of
317 * objects that match the specified filters, sort order and pagination.
318 * The other indices contain pagination information ("total" is the total
319 * number of objects after filtering but before applying "length", "length"
320 * is the requested number of items to return (as in the parameter list),
321 * "start" is the start index of the result in the total number of objects,
322 * and "page" is the page number (starting with 1) of pages with "length"
323 * objects each, or 0 if invalid).
325 mapping get_inventory_paginated ( array|void filters, array|void sort, int|void offset, int|void length )
327 oaInventory -= ({ 0 });
328 try_event(EVENT_GET_INVENTORY, CALLER);
329 run_event(EVENT_GET_INVENTORY, CALLER);
330 return get_module( "searching" )->paginate_object_array( oaInventory,
331 filters, sort, offset, length );
335 * Returns the inventory of this container, optionally filtered, sorted and
336 * limited by offset and length. This returns the same as the "objects" index
337 * in the result of get_inventory_paginated() and is here for compatibility
338 * reasons and ease of use (if you don't need pagination information).
340 * @see get_inventory_paginated
342 array get_inventory_filtered ( array|void filters, array|void sort, int|void offset, int|void length )
344 return get_inventory_paginated( filters, sort, offset, length )["objects"];
349 * Get only objects of a certain class. The class is the bit id submitted
350 * to the function. It matches only the highest class bit given.
351 * This means get_inventory_by_class(CLASS_CONTAINER) would not return
352 * any CLASS_ROOM. Also it is possible to do
353 * get_inventory_by_class(CLASS_CONTAINER|CLASS_EXIT) which would return
354 * an array of containers and exits, but still no rooms or links -
355 * Room is derived from Container and Exit inherits Link.
357 * @param int cl - the classid
358 * @param void|int from_obj - starting object
359 * @param void|int to_obj - second parameter for an object range.
360 * @return list of objects matching the given criteria.
362 array get_inventory_by_class(int cl, int|void from_obj,int|void to_obj)
364 try_event(EVENT_GET_INVENTORY, CALLER);
365 run_event(EVENT_GET_INVENTORY, CALLER);
369 for ( int i = 0; i < 32; i++ ) {
374 foreach(bits, int bit) {
375 foreach(oaInventory, object obj) {
378 int ocl = obj->get_object_class();
379 if ( (ocl & bit) && (ocl < (bit<<1)) ) {
381 if ( to_obj != 0 && cnt > to_obj ) break;
382 if ( from_obj < cnt && (to_obj == 0 || cnt < to_obj ) )
391 * Restore the container data. Most importantly the inventory.
393 * @param data - the unserialized object data
395 * @see store_container
398 void restore_container(mixed data)
400 if (CALLER != _Database )
401 THROW("Caller is not Database !", E_ACCESS);
403 oaInventory = data["Inventory"];
404 if ( !arrayp(oaInventory) )
412 * Stores the data of the container. Returns the inventory
415 * @return the inventory and possible other important container data.
417 * @see restore_container
420 mixed store_container()
422 if (CALLER != _Database )
423 THROW("Caller is not Database !", E_ACCESS);
424 return ([ "Inventory": oaInventory, ]);
430 * Get the content size of this object which does not make really
431 * sense for containers.
433 * @return the content size: -2 as the container can be seen as an inventory
436 int get_content_size()
442 * Get the size of documents in this container only or including sub-containers
444 * @param void|bool calculateContainers - also get size of container
445 * @return size of documents in container
447 int get_content_size_inventory(void|bool calculateContainers)
450 foreach(oaInventory, object inv) {
451 if ( objectp(inv) ) {
452 if ( inv->get_object_class() & CLASS_DOCUMENT )
453 size += inv->get_content_size();
454 else if ( calculateContainers &&
455 inv->get_object_class() & CLASS_CONTAINER )
456 size += inv->get_content_size_inventory();
463 * Get the number of objects in this container
465 * @return number of objects in this container
469 return sizeof(oaInventory);
473 * This function returns the stat() of this object. This has the
474 * same format as statting a file.
476 * @return status array as in file_stat()
477 * @see get_content_size
481 int creator_id = objectp(get_creator())?get_creator()->get_object_id():0;
483 return ({ 16895, get_content_size(),
484 do_query_attribute(OBJ_CREATION_TIME),
485 do_query_attribute(CONT_LAST_MODIFIED)||
486 do_query_attribute(OBJ_CREATION_TIME),
488 creator_id, creator_id, "httpd/unix-directory" });
492 * The function returns an array of important events used by this
493 * container. In order to observe the actions inside the container,
494 * the events should be heared.
496 * @return Array of relevant events
500 return ({ EVENT_SAY, EVENT_LEAVE_INVENTORY, EVENT_ENTER_INVENTORY });
504 * This function sends a message to the container, which actually
505 * means the say event is fired and we can have a conversation between
506 * users inside this container.
508 * @param msg - the message to say
510 bool message(string msg)
512 /* does almost nothing... */
513 try_event(EVENT_SAY, CALLER, msg);
514 run_event(EVENT_SAY, CALLER, msg);
519 * Swap the position of two objects in the inventory. This
520 * function is usefull for reordering the inventory.
521 * You can sort an inventory afterwards or use the order of
522 * objects given in the list (array).
524 * @param int|object from - the object or position "from"
525 * @param int|object to - the object or position to swap to
526 * @return if successfull or not (error)
531 bool swap_inventory(int|object from, int|object to)
533 int sz = sizeof(oaInventory);
536 from = search(oaInventory, from);
538 to = search(oaInventory, to);
540 ASSERTINFO(from >= 0 && from < sz && to >= 0 && to < sz,
541 "False position for inventory swapping !");
542 if ( from == to ) return true;
543 object from_obj = oaInventory[from];
544 object to_obj = oaInventory[to];
545 oaInventory[from] = to_obj;
546 oaInventory[to] = from_obj;
547 require_save(STORE_CONTAINER, UNDEFINED, SAVE_ORDER);
552 * Changes the order of the inventory by passing an order array,
553 * the standard pike sort function is used for this and sorts
554 * the array the same way order is sorted (integer values, by numbers).
556 * @param array order - the sorting order.
557 * @return whether sorting was successfull or not.
559 bool order_inventory(array order)
561 int size = sizeof( oaInventory );
562 ASSERTINFO( sizeof(order) == size, "Size of order array does not match !" );
564 array sorter = allocate( size );
565 for ( int i=0; i<size; i++ ) {
566 if ( order[i] != i ) sorter[ (int)order[i] ] = i;
569 sort(sorter, oaInventory);
570 require_save(STORE_CONTAINER, UNDEFINED, SAVE_ORDER);
574 bool order_inventory_objects ( array objects )
576 if ( !arrayp(objects) )
577 THROW( "Not an array !", E_ERROR );
578 array old_indices = ({ });
579 array tmp_objects = ({ });
580 foreach ( objects, mixed obj ) {
581 if ( !objectp(obj) ) catch( obj = find_object( (int)obj ) );
583 THROW( "Invalid object/id !", E_ERROR );
584 int index = search( oaInventory, obj );
586 THROW( sprintf( "Object %d not in inventory !", obj->get_object_id() ),
588 old_indices += ({ index });
589 tmp_objects += ({ obj });
591 array new_indices = sort( old_indices );
592 for ( int i=0; i<sizeof(old_indices); i++ )
593 oaInventory[ new_indices[i] ] = tmp_objects[i];
594 require_save(STORE_CONTAINER, UNDEFINED, SAVE_ORDER);
599 * Get an object by its name from the inventory of this Container.
601 * @param string obj_name - the object to get
602 * @return 0|object found by the given name
604 * @see get_inventory_by_class
606 object get_object_byname(string obj_name, object|void o)
608 oaInventory -= ({ 0 });
610 if ( !stringp(obj_name) )
613 foreach ( oaInventory, object obj ) {
614 if ( objectp(o) && o == obj ) continue;
616 obj = obj->get_object();
617 if ( !objectp(obj) ) continue;
618 if ( obj->get_object_class() & CLASS_USER )
619 continue; // skip user objects
620 if (functionp(obj->get_identifier) && obj_name==obj->get_identifier())
625 bool contains(object obj, bool rec)
627 if ( search(oaInventory, obj) >= 0 )
630 foreach(oaInventory, object o) {
631 if ( objectp(o) && o->get_object_class() & CLASS_CONTAINER )
632 if ( o->contains(obj, true) )
640 * Get the users present in this Room. There shouldnt be any User
641 * inside a Container.
643 * @return array of users.
648 foreach(get_inventory(), object inv) {
649 if ( inv->get_object_class() & CLASS_USER )
655 void lowAppendXML(object rootNode, void|int depth)
657 ::lowAppendXML(rootNode, depth);
659 foreach(oaInventory, object o) {
661 object objNode = xslt.Node("Object", ([ ]));
662 rootNode->add_child(objNode);
663 o->lowAppendXML(objNode, depth-1);
670 * Get the object class of Container.
672 * @return the object class of container. Check with CLASS_CONTAINER.
674 int get_object_class()
676 return ::get_object_class() | CLASS_CONTAINER;
680 * Is this an object ? yes!
684 final bool is_container() { return true; }
689 return get_identifier()+"(#"+get_object_id()+","+
690 master()->describe_program(object_program(this_object()))+","+
691 get_object_class()+","+sizeof(oaInventory)+" objects)";