PageRenderTime 91ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/Adapto/src/Adapto/Entity.php

http://github.com/egeniq/adapto
PHP | 4713 lines | 2431 code | 494 blank | 1788 comment | 475 complexity | 7994ef1883eb7656025eced0247d6d35 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Adapto Toolkit.
  4. * Detailed copyright and licensing information can be found
  5. * in the doc/COPYRIGHT and doc/LICENSE files which should be
  6. * included in the distribution.
  7. *
  8. * @package adapto
  9. *
  10. * @copyright (c)2000-2007 Ivo Jansch
  11. * @copyright (c)2000-2008 Ibuildings.nl BV
  12. * @license http://www.achievo.org/atk/licensing ATK Open Source License
  13. *
  14. */
  15. /**
  16. * some includes
  17. */
  18. /**
  19. * Define some flags for entitys. Use the constructor of the Adapto_Entity
  20. * class to set the flags. (concatenate multiple flags with '|')
  21. */
  22. /**
  23. * No new records may be added
  24. */
  25. define("EF_NO_ADD", 1);
  26. /**
  27. * Records may not be edited
  28. */
  29. define("EF_NO_EDIT", 2);
  30. /**
  31. * Records may not be deleted
  32. */
  33. define("EF_NO_DELETE", 4);
  34. /**
  35. * Immediately after you add a new record,
  36. * you get the editpage for that record
  37. */
  38. define("EF_EDITAFTERADD", 8);
  39. /**
  40. * Records may not be searched
  41. */
  42. define("EF_NO_SEARCH", 16);
  43. /**
  44. * Ignore addFilter filters
  45. */
  46. define("EF_NO_FILTER", 32);
  47. /**
  48. * Doesn't show an add form on the admin page
  49. * but a link to the form
  50. */
  51. define("EF_ADD_LINK", 64);
  52. /**
  53. * Records may not be viewed
  54. */
  55. define("EF_NO_VIEW", 128);
  56. /**
  57. * Records / trees may be copied
  58. */
  59. define("EF_COPY", 256);
  60. /**
  61. * If this flag is set and only one record is
  62. * present on a selectpage, atk automagically
  63. * selects it and moves on to the target
  64. */
  65. define("EF_AUTOSELECT", 512);
  66. /**
  67. * If set, atk stores the old values of
  68. * a record as ["atkorgrec"] in the $rec that
  69. * gets passed to the postUpdate
  70. */
  71. define("EF_TRACK_CHANGES", 1024);
  72. /**
  73. * Quick way to disable accessright checking
  74. * for an entire entity. (Everybody may access this entity)
  75. */
  76. define("EF_NO_SECURITY", 2048);
  77. /**
  78. * Extended search feature is turned off
  79. */
  80. define("EF_NO_EXTENDED_SEARCH", 4096);
  81. /**
  82. * Multi-selection of records is turned on
  83. */
  84. define("EF_MULTI_RECORD_ACTIONS", 8192);
  85. /**
  86. * Multi-priority-selection of records is turned on
  87. */
  88. define("EF_MRPA", 16384);
  89. /**
  90. * Add locking support to entity, if one user is editing a record,
  91. * no one else may edit it.
  92. */
  93. define("EF_LOCK", 32768);
  94. /**
  95. * Multi-language support
  96. */
  97. define("EF_ML", 65536);
  98. /**
  99. * Quick way to ensable the csv import feature
  100. */
  101. define("EF_IMPORT", 131072);
  102. /**
  103. * Add CSV export ability to the entity.
  104. */
  105. define("EF_EXPORT", 262144);
  106. /**
  107. * Enable extended sorting (multicolumn sort)
  108. */
  109. define("EF_EXT_SORT", 524288);
  110. /**
  111. * Makes an entity cache it's recordlist
  112. */
  113. define("EF_CACHE_RECORDLIST", 1048576);
  114. /**
  115. * After adding a new record add another one instantaniously.
  116. */
  117. define("EF_ADDAFTERADD", 2097152);
  118. /**
  119. * No sorting possible.
  120. */
  121. define("EF_NO_SORT", 4194304);
  122. /**
  123. * Use the dialog popup box when adding a new record for this entity.
  124. */
  125. define("EF_ADD_DIALOG", 8388608);
  126. /**
  127. * Use the dialog add-or-copy popup box when adding a new record for this entity.
  128. */
  129. define("EF_ADDORCOPY_DIALOG", 16777216);
  130. /**
  131. * Specific entity flag 1
  132. */
  133. define("EF_SPECIFIC_1", 33554432);
  134. /**
  135. * Specific entity flag 2
  136. */
  137. define("EF_SPECIFIC_2", 67108864);
  138. /**
  139. * Specific entity flag 3
  140. */
  141. define("EF_SPECIFIC_3", 134217728);
  142. /**
  143. * Specific entity flag 4
  144. */
  145. define("EF_SPECIFIC_4", 268435456);
  146. /**
  147. * Specific entity flag 5
  148. */
  149. define("EF_SPECIFIC_5", 536870912);
  150. /**
  151. * Records may be copied and open for editing
  152. */
  153. define("EF_EDITAFTERCOPY", 1073741824);
  154. /**
  155. * Alias for EF_MULTI_RECORD_ACTIONS flag (shortcut)
  156. */
  157. define("EF_MRA", EF_MULTI_RECORD_ACTIONS);
  158. /**
  159. * Alias for EF_ML flag (typed out)
  160. */
  161. define("EF_MULTILANGUAGE", EF_ML);
  162. /**
  163. * Aggregate flag to quickly create readonly entitys
  164. */
  165. define("EF_READONLY", EF_NO_ADD | EF_NO_DELETE | EF_NO_EDIT);
  166. /**
  167. * action status flags
  168. * Note that these have binary numbers, even though an action could never have
  169. * two statusses at the same time.
  170. * This is done however, so the flags can be used as a mask in the setFeedback
  171. * function.
  172. */
  173. /**
  174. * The action is cancelled
  175. *
  176. * action status flag
  177. */
  178. define("ACTION_CANCELLED", 1);
  179. /**
  180. * The action failed to accomplish it's goal
  181. *
  182. * action status flag
  183. */
  184. define("ACTION_FAILED", 2);
  185. /**
  186. * The action is a success
  187. *
  188. * action status flag
  189. */
  190. define("ACTION_SUCCESS", 4);
  191. /**
  192. * Trigger flags
  193. */
  194. define("TRIGGER_NONE", 0);
  195. define("TRIGGER_AUTO", 1);
  196. define("TRIGGER_PRE", 2);
  197. define("TRIGGER_POST", 4);
  198. define("TRIGGER_ALL", TRIGGER_PRE | TRIGGER_POST);
  199. /**
  200. * Multi-record-actions selection modes. These
  201. * modes are mutually exclusive.
  202. */
  203. /**
  204. * Multiple selections possible.
  205. */
  206. define("MRA_MULTI_SELECT", 1);
  207. /**
  208. * Only one selection possible.
  209. */
  210. define("MRA_SINGLE_SELECT", 2);
  211. /**
  212. * No selection possible (e.g. action is always for all (visible) records!).
  213. */
  214. define("MRA_NO_SELECT", 3);
  215. /**
  216. * The Adapto_Entity class represents a piece of information that is part of an
  217. * application. This class provides standard functionality for adding,
  218. * editing and deleting entitys.
  219. * This class must be seen as an abstract base class: For every piece of
  220. * information in an application, a class must be derived from this class
  221. * with specific implementations for that type of entity.
  222. *
  223. * @author ijansch
  224. * @package adapto
  225. */
  226. class Adapto_Entity
  227. {
  228. /**
  229. * reference to the class which is used to validate atkentitys
  230. * the validator is overridable by changing this variabele
  231. *
  232. * @var String
  233. */
  234. private $m_validate_class = "Adapto_EntityValidator";
  235. /**
  236. * Unique field sets of a certain entity.
  237. *
  238. * Indicates which field combinations should be unique.
  239. * It doesn't contain the unique fields which have been set by flag
  240. * AF_UNIQUE.
  241. *
  242. * @var array
  243. */
  244. private $m_uniqueFieldSets = array();
  245. /**
  246. * Entitys must be initialised using the init() function before they can be
  247. * used. This member indicated whether the entity has been initialised.
  248. * @var boolean
  249. */
  250. private $m_initialised = false;
  251. /**
  252. * Check to prevent double execution of setAttribSizes on pages with more
  253. * than one form.
  254. * @var boolean
  255. */
  256. private $m_attribsizesset = false;
  257. /**
  258. * The list of attributes of an entity. These should be of the class
  259. * atkAttribute or one of its derivatives.
  260. * @var array
  261. */
  262. private $m_attribList = array();
  263. /**
  264. * Index list containing the attributes in the order in which they will
  265. * appear on screen.
  266. * @var array
  267. */
  268. private $m_attribIndexList = array();
  269. /**
  270. * Reference to the page on which the entity is rendering its output.
  271. * @var atkPage
  272. */
  273. private $m_page = NULL;
  274. /**
  275. * List of available tabs. Associative array structured like this:
  276. * array($action=>$arrayOfTabnames)
  277. * @var array
  278. */
  279. private $m_tabList = array();
  280. /**
  281. * List of available sections. Associative array structured like this:
  282. * array($action=>$arrayOfSectionnames)
  283. * @var array
  284. */
  285. private $m_sectionList = array();
  286. /**
  287. * Keep track of tabs per attribute.
  288. * @var array
  289. */
  290. private $m_attributeTabs = array();
  291. /**
  292. * Keep track if a tab contains attribs (checkEmptyTabs function)
  293. * @var array
  294. */
  295. private $m_filledTabs = array();
  296. /**
  297. * The entitytype.
  298. * @var String
  299. */
  300. protected $m_type;
  301. /**
  302. * The module of the entity.
  303. * @var String
  304. */
  305. protected $m_module;
  306. /**
  307. * The database that the entity is using for storing and loading its data.
  308. * @var mixed
  309. */
  310. protected $m_db = NULL;
  311. /**
  312. * The table to use for data storage.
  313. * @var String
  314. */
  315. protected $m_table;
  316. /**
  317. * The name of the sequence used for autoincrement fields.
  318. * @var String
  319. */
  320. protected $m_seq;
  321. /**
  322. * Name of the attribute that contains the language of a record.
  323. * Used with ATK's data internationalization feature.
  324. * @var String
  325. */
  326. private $m_lngfield;
  327. /**
  328. * List of names of the attributes that form this entity's primary key.
  329. * @var array
  330. */
  331. protected $m_primaryKey = array();
  332. /**
  333. * The postvars (or getvars) that are passed to a page will be passed
  334. * to the class using the dispatch function. We store them in a member
  335. * variable for easy access.
  336. * @var array
  337. */
  338. protected $m_postvars = array();
  339. /**
  340. * The action that the entity is currently performing.
  341. * @var String
  342. */
  343. protected $m_action;
  344. /**
  345. * Contains the definition of what needs to rendered partially.
  346. * If set to NULL not in partial rendering mode.
  347. */
  348. public $m_partial = NULL; // defaulted to public
  349. /**
  350. * The active action handler.
  351. * @var atkActionHandler
  352. */
  353. protected $m_handler = NULL;
  354. /**
  355. * Default order by statement.
  356. * @var String
  357. */
  358. protected $m_default_order = "";
  359. /**
  360. * var used for tracking relation within this entity.
  361. * @todo Remove this member, it's using memory while it's used only in
  362. * the case of multilanguage entity, and even then only on one
  363. * occasion.
  364. * @var array
  365. */
  366. private $m_relations = array();
  367. /**
  368. * Bitwise mask of entity flags (EF_* flags).
  369. * @var int
  370. */
  371. public $m_flags; // defaulted to public
  372. /*
  373. * Name of the field that is used for creating an alphabetical index in
  374. * admin/select pages.
  375. * @var String
  376. */
  377. private $m_index = "";
  378. /**
  379. * Default tab being displayed in add/edit mode.
  380. * @var String
  381. */
  382. private $m_default_tab = "default";
  383. /**
  384. * Default sections that are expanded.
  385. * @var String
  386. */
  387. private $m_default_expanded_sections = array();
  388. /**
  389. * Record filters, in attributename/required value pairs.
  390. * @var array
  391. */
  392. private $m_filters = array();
  393. /**
  394. * Record filters, as a list of sql statements.
  395. * @var array
  396. */
  397. private $m_fuzzyFilters = array();
  398. /**
  399. * For speed, we keep track of a list of attributes that we don't have to
  400. * load in recordlists.
  401. * @var array
  402. */
  403. protected $m_listExcludes = array();
  404. /**
  405. * For speed, we keep track of a list of attributes that we don't have to
  406. * load when in view pages.
  407. * @todo This can probably be moved to the view handler.
  408. * @var array
  409. */
  410. protected $m_viewExcludes = array();
  411. /**
  412. * For speed, we keep track of a list of attributes that have the cascade
  413. * delete flag set.
  414. * @todo This should be moved to the delete handler, or should not be
  415. * cached at all. (caching this on each load is slower than just
  416. * retrieving the list when it's needed)
  417. * @var array
  418. */
  419. private $m_cascadingAttribs = array();
  420. /**
  421. * Actions are mapped to security units.
  422. *
  423. * For example, both actions "save" and "add" require access "add". If an
  424. * item is not in this list, it's treated 'as-is'. Derived entitys may add
  425. * more mappings to tell the systems that some custom actions require the
  426. * same privilege as others.
  427. * Structure: array($action=>$requiredPrivilege)
  428. * @var array
  429. */
  430. protected $m_securityMap = array("save" => "add", "update" => "edit", "multiupdate" => "edit", "copy" => "add", "import" => "add", "editcopy" => "add",
  431. "search" => "admin", "smartsearch" => "admin");
  432. /**
  433. * The right to execute certain actions can be implied by the fact that you
  434. * have some other right. For example, if you have the right to access a
  435. * feature (admin right), you may also view that record, and don't need
  436. * explicit rights to view it. So the 'view' right is said to be 'implied'
  437. * by the 'admin' right.
  438. * This is a subtle difference with m_securityMap.
  439. * @var array
  440. */
  441. protected $m_securityImplied = array("view" => "admin");
  442. /**
  443. * Name of the entity that is used for privilege checking.
  444. *
  445. * If a class is named 'project', then by default, if the system needs to
  446. * know whether a user may edit a record, the securitymanager searches
  447. * for 'edit' access on 'project'. However, if an alias is set here, the
  448. * securitymanger searches for 'edit' on that alias.
  449. * @var String
  450. */
  451. private $m_securityAlias = "";
  452. /*
  453. * Entitys can specify actions that require no access level
  454. * Note: for the moment, the "select" action is always allowed.
  455. * @todo This may not be correct. We have to find a way to bind the
  456. * select action to the action that follows after the select.
  457. * @var array
  458. */
  459. private $m_unsecuredActions = array("select", "multiselect", "feedback");
  460. /**
  461. * Auto search-actions; action that will be performed if only one record
  462. * is found.
  463. * @var array
  464. */
  465. private $m_search_action;
  466. /**
  467. * Priority actions
  468. * @todo This, and the priority_min/max members, should be moved
  469. * to the recordlist
  470. * @var array
  471. */
  472. private $m_priority_actions = array();
  473. /**
  474. * Minimum for the mra priority select
  475. * @var int
  476. */
  477. private $m_priority_min = 1;
  478. /**
  479. * Maximum for the mra priority select
  480. * @var int
  481. */
  482. private $m_priority_max = 0;
  483. /**
  484. * The lock instance
  485. * @var atkLock
  486. */
  487. protected $m_lock = NULL;
  488. /**
  489. * List of actions that should give success/failure feedback
  490. * @var array
  491. */
  492. private $m_feedback = array();
  493. /**
  494. * Default language used by Multilanguage Entitys.
  495. * @var String
  496. */
  497. protected $m_defaultlanguage = "";
  498. /**
  499. * Number to use with numbering
  500. * @var mixed
  501. */
  502. protected $m_numbering = null;
  503. /**
  504. * Descriptor template.
  505. * @var String
  506. */
  507. protected $m_descTemplate = NULL;
  508. /**
  509. * Descriptor handler.
  510. * @var Object
  511. */
  512. protected $m_descHandler = NULL;
  513. /**
  514. * List of action listeners
  515. * @var Array
  516. */
  517. protected $m_actionListeners = array();
  518. /**
  519. * List of trigger listeners
  520. * @var Array
  521. */
  522. protected $m_triggerListeners = array();
  523. /**
  524. * List of callback functions to manipulate the record actions
  525. *
  526. * @var array
  527. */
  528. protected $m_recordActionsCallbacks = array();
  529. /**
  530. * List of callback functions to add css class to row.
  531. * See details in atkDGList::getRecordlistData() method
  532. *
  533. * @var array
  534. */
  535. protected $m_rowClassCallback = array();
  536. /**
  537. * Tracker variable to see if we are currently in 'modifier mode' (running inside
  538. * the scope of a modname_entityname_modifier() method). The variable contains the
  539. * name of the modifying module.
  540. * @var String
  541. */
  542. private $m_modifier = "";
  543. /**
  544. * Extended search action. The action which is called if the user
  545. * wants to perform an extended search.
  546. *
  547. * @var String
  548. */
  549. private $m_extended_search_action = NULL;
  550. /**
  551. * List of editable list attributes.
  552. * @var Array
  553. */
  554. private $m_editableListAttributes = array();
  555. /**
  556. * Multi-record actions, selection mode.
  557. * @var int
  558. */
  559. private $m_mraSelectionMode = MRA_MULTI_SELECT;
  560. /**
  561. * The default edit fieldprefix to use for atk
  562. * @var String
  563. */
  564. private $m_edit_fieldprefix = '';
  565. /**
  566. * Lock mode.
  567. *
  568. * @var int
  569. */
  570. private $m_lockMode = 'exclusive'; // atkLock::EXCLUSIVE (would mean atkLock needs to be available!)
  571. /**
  572. * Default column name (null means across all columns)
  573. *
  574. * @var string
  575. */
  576. private $m_defaultColumn = null;
  577. /**
  578. * Current maximum attribute order value.
  579. *
  580. * @var int
  581. */
  582. private $m_attribOrder = 0;
  583. /**
  584. * Constructor.
  585. *
  586. * This initialises the entity. Derived classes should always call their
  587. * parent constructor ($this->Adapto_Entity($name, $flags), to initialize the
  588. * base class.
  589. * <br>
  590. * <b>Example:</b>
  591. * <code>$this->Adapto_Entity('test',EF_NO_EDIT);</code>
  592. * @param String $type The entitytype (by default equal to the classname)
  593. * @param int $flags Bitmask of entity flags (EF_*).
  594. */
  595. public function __construct($type = "", $flags = 0)
  596. {
  597. if ($type == "")
  598. $type = strtolower(get_class($this));
  599. Adapto_Util_Debugger::debug("Creating a new Adapto_Entity for $type");
  600. $this->m_type = $type;
  601. $this->m_flags = $flags;
  602. $this->m_module = Adapto_Module::getModuleScope();
  603. $this->setEditFieldPrefix(Adapto_Config::getGlobal('edit_fieldprefix', ''));
  604. }
  605. /**
  606. * Resolve section. If a section is only prefixed by
  607. * a dot this means we need to add the default tab
  608. * before the dot.
  609. *
  610. * @param string $section section name
  611. * @return resolved section name
  612. */
  613. function resolveSection($section)
  614. {
  615. list($part1, $part2) = (strpos($section, ".") !== false) ? explode('.', $section) : array($section, "");
  616. if ($part2 != NULL && strlen($part2) > 0 && strlen($part1) == 0)
  617. return $this->m_default_tab . "." . $part2;
  618. else if (strlen($part2) == 0 && strlen($part1) == 0)
  619. return $this->m_default_tab;
  620. else
  621. return $section;
  622. }
  623. /**
  624. * Resolve sections.
  625. *
  626. * @param array $sections section list
  627. * @return array resolved section list
  628. *
  629. * @see resolveSection
  630. */
  631. function resolveSections($sections)
  632. {
  633. $result = array();
  634. foreach ($sections as $section) {
  635. $result[] = $this->resolveSection($section);
  636. }
  637. return $result;
  638. }
  639. /**
  640. * Returns the default column name.
  641. *
  642. * @return string default column name
  643. */
  644. public function getDefaultColumn()
  645. {
  646. return $this->m_defaultColumn;
  647. }
  648. /**
  649. * Set default column name.
  650. *
  651. * @param string name default column name
  652. */
  653. public function setDefaultColumn($name)
  654. {
  655. $this->m_defaultColumn = $name;
  656. }
  657. /**
  658. * Resolve column for sections.
  659. *
  660. * If one of the sections contains something after a double
  661. * colon (:) than that's used as column name, else the default
  662. * column name will be used.
  663. *
  664. * @param array $sections sections
  665. *
  666. * @return string column name
  667. */
  668. protected function resolveColumn(&$sections)
  669. {
  670. $column = $this->getDefaultColumn();
  671. if (!is_array($sections)) {
  672. return $column;
  673. }
  674. foreach ($sections as &$section) {
  675. if (strpos($section, ":") !== false) {
  676. list($section, $column) = explode(':', $section);
  677. }
  678. }
  679. return $column;
  680. }
  681. /**
  682. * Resolve sections, tabs and the order based on the given
  683. * argument to the attribute add method.
  684. *
  685. * @param mixed $sections
  686. * @param mixed $tabs
  687. * @param mixed $order
  688. */
  689. function resolveSectionsTabsOrder(&$sections, &$tabs, &$column, &$order)
  690. {
  691. // Because sections/tabs will probably be used more than the order override option
  692. // the API for this method now favours the $sections argument. For backwards
  693. // compatibility we still support the old API ($attribute,$order=0).
  694. if ($sections !== NULL && is_int($sections)) {
  695. $order = $sections;
  696. $sections = array($this->m_default_tab);
  697. }
  698. // If no section/tab is specified or tabs are disabled, we use the current default tab
  699. // (specified with the setDefaultTab method, or "default" otherwise)
  700. elseif ($sections === NULL || (is_string($sections) && strlen($sections) == 0) || !Adapto_Config::getGlobal("tabs")) {
  701. $sections = array($this->m_default_tab);
  702. }
  703. // Sections should be an array.
  704. else if ($sections != "*" && !is_array($sections)) {
  705. $sections = array($sections);
  706. }
  707. $column = $this->resolveColumn($sections);
  708. if (is_array($sections)) {
  709. $sections = $this->resolveSections($sections);
  710. }
  711. // Filter tabs from section names.
  712. $tabs = $this->getTabsFromSections($sections);
  713. }
  714. /**
  715. * Add an atkAttribute (or one of its derivatives) to the entity.
  716. * @param atkAttribute $attribute The attribute you want to add
  717. * @param mixed $sections The sections/tab(s) on which the attribute should be
  718. * displayed. Can be a tabname (String) or a list of
  719. * tabs (array) or "*" if the attribute should be
  720. * displayed on all tabs.
  721. * @param int $order The order at which the attribute should be displayed.
  722. * If ommitted, this defaults to 100 for the first
  723. * attribute, and 100 more for each next attribute that
  724. * is added.
  725. * @return atkAttribute the attribute just added
  726. */
  727. public function add($attribute, $sections = NULL, $order = 0)
  728. {
  729. $tabs = null;
  730. $column = null;
  731. $attribute->m_owner = $this->m_type;
  732. // If we're running inside modifier scope, we have to tell the attribute
  733. // what module he originated from.
  734. if ($this->m_modifier != "")
  735. $attribute->m_module = $this->m_modifier;
  736. if (!atkReadOptimizer()) {
  737. $this->resolveSectionsTabsOrder($sections, $tabs, $column, $order);
  738. // check for parent fieldname (treeview)
  739. if ($attribute->hasFlag(AF_PARENT)) {
  740. $this->m_parent = $attribute->fieldName();
  741. }
  742. // check for cascading delete flag
  743. if ($attribute->hasFlag(AF_CASCADE_DELETE)) {
  744. $this->m_cascadingAttribs[] = $attribute->fieldName();
  745. }
  746. if ($attribute->hasFlag(AF_HIDE_LIST) && !$attribute->hasFlag(AF_PRIMARY)) {
  747. if (!in_array($attribute->fieldName(), $this->m_listExcludes)) {
  748. $this->m_listExcludes[] = $attribute->fieldName();
  749. }
  750. }
  751. if ($attribute->hasFlag(AF_HIDE_VIEW) && !$attribute->hasFlag(AF_PRIMARY)) {
  752. if (!in_array($attribute->fieldName(), $this->m_viewExcludes)) {
  753. $this->m_viewExcludes[] = $attribute->fieldName();
  754. }
  755. }
  756. } else {
  757. // when the read optimizer is enabled there is no active tab
  758. // we circument this by putting all attributes on all tabs
  759. if ($sections !== NULL && is_int($sections))
  760. $order = $sections;
  761. $tabs = "*";
  762. $sections = "*";
  763. $column = $this->getDefaultColumn();
  764. }
  765. // NOTE: THIS SHOULD WORK. BUT, since add() is called from inside the $this
  766. // constructor, m_ownerInstance ends up being a copy of $this, rather than
  767. // a reference. Don't ask me why, it has something to do with the way PHP
  768. // handles the constructor.
  769. // To work around this, we reassign the this pointer to the attributes as
  770. // soon as possible AFTER the constructor. (the dispatcher function)
  771. $attribute->setOwnerInstance($this);
  772. if ($attribute->hasFlag(AF_PRIMARY)) {
  773. if (!in_array($attribute->fieldName(), $this->m_primaryKey)) {
  774. $this->m_primaryKey[] = $attribute->fieldName();
  775. }
  776. }
  777. if ($attribute->hasFlag(AF_MULTILANGUAGE)) {
  778. $this->m_lngfield = $attribute->fieldName();
  779. }
  780. $attribute->init();
  781. $exist = false;
  782. if (isset($this->m_attribList[$attribute->fieldName()]) && is_object($this->m_attribList[$attribute->fieldName()])) {
  783. $exist = true;
  784. // if order is set, overwrite it with new order, last order will count
  785. if ($order != 0) {
  786. $this->m_attribIndexList[$this->m_attribList[$attribute->fieldName()]->m_index]["order"] = $order;
  787. }
  788. $attribute->m_index = $this->m_attribList[$attribute->fieldName()]->m_index;
  789. }
  790. if (!$exist) {
  791. if ($order == 0) {
  792. $this->m_attribOrder += 100;
  793. $order = $this->m_attribOrder;
  794. }
  795. if (!atkReadOptimizer()) {
  796. // add new tab(s) to the tab list ("*" isn't a tab!)
  797. if ($tabs != "*") {
  798. if (!$attribute->hasFlag(AF_HIDE_ADD))
  799. $this->m_tabList["add"] = isset($this->m_tabList["add"]) ? Adapto_array_merge($this->m_tabList["add"], $tabs) : $tabs;
  800. if (!$attribute->hasFlag(AF_HIDE_EDIT))
  801. $this->m_tabList["edit"] = isset($this->m_tabList["edit"]) ? Adapto_array_merge($this->m_tabList["edit"], $tabs) : $tabs;
  802. if (!$attribute->hasFlag(AF_HIDE_VIEW))
  803. $this->m_tabList["view"] = isset($this->m_tabList["view"]) ? Adapto_array_merge($this->m_tabList["view"], $tabs) : $tabs;
  804. }
  805. if ($sections != "*") {
  806. if (!$attribute->hasFlag(AF_HIDE_ADD))
  807. $this->m_sectionList["add"] = isset($this->m_sectionList["add"]) ? Adapto_array_merge($this->m_sectionList["add"], $sections)
  808. : $sections;
  809. if (!$attribute->hasFlag(AF_HIDE_EDIT))
  810. $this->m_sectionList["edit"] = isset($this->m_sectionList['edit']) ? Adapto_array_merge($this->m_sectionList["edit"], $sections)
  811. : $sections;
  812. if (!$attribute->hasFlag(AF_HIDE_VIEW))
  813. $this->m_sectionList["view"] = isset($this->m_sectionList['view']) ? Adapto_array_merge($this->m_sectionList["view"], $sections)
  814. : $sections;
  815. }
  816. }
  817. $attribute->m_order = $order;
  818. $this->m_attribIndexList[] = array("name" => $attribute->fieldName(), "tabs" => $tabs, "sections" => $sections, "order" => $attribute->m_order);
  819. $attribute->m_index = max(array_keys($this->m_attribIndexList)); // might contain gaps
  820. $attribute->setTabs($tabs);
  821. $attribute->setSections($sections);
  822. $this->m_attributeTabs[$attribute->fieldname()] = $tabs;
  823. }
  824. // Order the tablist
  825. $this->m_attribList[$attribute->fieldName()] = &$attribute;
  826. if (is_subclass_of($attribute, "atkrelation")) {
  827. $this->m_relations[strtolower(get_class($attribute))][$attribute->fieldName()] = &$attribute;
  828. }
  829. $attribute->setTabs($this->m_attributeTabs[$attribute->fieldName()]);
  830. $attribute->setSections($this->m_attribIndexList[$attribute->m_index]['sections']);
  831. $attribute->setColumn($column);
  832. return $attribute;
  833. }
  834. /**
  835. * Add fieldset.
  836. *
  837. * To include an attribute label use [attribute.label] inside your
  838. * template. To include an attribute edit/display field use
  839. * [attribute.field] inside your template.
  840. *
  841. * @param string $name name
  842. * @param string $template template string
  843. * @param int $flags attribute flags
  844. * @param mixed $sections The sections/tab(s) on which the attribute should be
  845. * displayed. Can be a tabname (String) or a list of
  846. * tabs (array) or "*" if the attribute should be
  847. * displayed on all tabs.
  848. * @param int $order The order at which the attribute should be displayed.
  849. * If ommitted, this defaults to 100 for the first
  850. * attribute, and 100 more for each next attribute that
  851. * is added.
  852. */
  853. public function addFieldSet($name, $template, $flags = 0, $sections = NULL, $order = 0)
  854. {
  855. useattrib('atkfieldset');
  856. $this->add(new Adapto_FieldSet($name, $template, $flags), $sections, $order);
  857. }
  858. /**
  859. * Retrieve the tabnames from the sections string (tab.section).
  860. *
  861. * @param mixed $sections An array with sections or a section string
  862. */
  863. function getTabsFromSections($sections)
  864. {
  865. if ($sections == "*" || $sections === NULL)
  866. return $sections;
  867. $tabs = array();
  868. if (!isset($sections))
  869. $section = array();
  870. elseif (!is_array($sections))
  871. $sections = array($sections);
  872. foreach ($sections as $section) {
  873. $tabs[] = $this->getTabFromSection($section);
  874. }
  875. //when using the tab.sections notation, we can have duplicate tabs
  876. //strip them out.
  877. return array_unique($tabs);
  878. }
  879. /**
  880. * Strip section part from a section and return the tab.
  881. *
  882. * If no tab name is provided, the default tab is returned.
  883. *
  884. * @param string $section The section to get the tab from
  885. */
  886. function getTabFromSection($section)
  887. {
  888. $tab = ($section == NULL) ? "" : $section;
  889. if (strstr($tab, ".") !== false)
  890. list($tab) = explode(".", $tab);
  891. return (($tab == "") ? $this->m_default_tab : $tab);
  892. }
  893. /**
  894. * Remove an attribute.
  895. *
  896. * Completely removes an attribute from an entity.
  897. * Note: Since other functionality may already depend on the attribute
  898. * that you are about to remove, it's often better to just hide an
  899. * attribute if you don't need it.
  900. * @param String $attribname The name of the attribute to remove.
  901. */
  902. function remove($attribname)
  903. {
  904. if (is_object($this->m_attribList[$attribname])) {
  905. Adapto_Util_Debugger::debug("removing attribute $attribname");
  906. $listindex = $this->m_attribList[$attribname]->m_index;
  907. unset($this->m_attribList[$attribname]);
  908. foreach ($this->m_listExcludes as $i => $name) {
  909. if ($name == $attribname)
  910. unset($this->m_listExcludes[$i]);
  911. }
  912. foreach ($this->m_viewExcludes as $i => $name) {
  913. if ($name == $attribname)
  914. unset($this->m_viewExcludes[$i]);
  915. }
  916. foreach ($this->m_cascadingAttribs as $i => $name) {
  917. if ($name == $attribname) {
  918. unset($this->m_cascadingAttribs[$i]);
  919. $this->m_cascadingAttribs = array_values($this->m_cascadingAttribs);
  920. }
  921. }
  922. unset($this->m_attribIndexList[$listindex]);
  923. unset($this->m_attributeTabs[$attribname]);
  924. }
  925. }
  926. /**
  927. * Returns the table name for this entity.
  928. *
  929. * @return string table name
  930. */
  931. function getTable()
  932. {
  933. return $this->m_table;
  934. }
  935. /**
  936. * Get an attribute by name.
  937. * @param String $name The name of the attribute to retrieve.
  938. * @return atkAttribute The attribute.
  939. */
  940. function &getAttribute($name)
  941. {
  942. $returnValue = isset($this->m_attribList[$name]) ? $this->m_attribList[$name] : NULL;
  943. return $returnValue;
  944. }
  945. /**
  946. * Checks if the user has filled in something:
  947. * return true if he has, otherwise return false
  948. *
  949. * @param -
  950. * @return boolean.
  951. */
  952. function &filledInForm()
  953. {
  954. if (is_null($this->getAttributes()))
  955. return false;
  956. $postvars = atkGetPostVar();
  957. foreach ($this->m_attribList AS $name => $value)
  958. if (!$value->hasFlag(AF_HIDE_LIST))
  959. if (!is_array($value->fetchValue($postvars)) && $value->fetchValue($postvars) !== "")
  960. return true;
  961. return false;
  962. }
  963. /**
  964. * Gets all the attributes.
  965. * @return array Array with the attributes.
  966. */
  967. function &getAttributes()
  968. {
  969. if (isset($this->m_attribList))
  970. return $this->m_attribList;
  971. else
  972. return NULL;
  973. }
  974. /**
  975. * Returns a list of attribute names.
  976. *
  977. * @return array attribute names
  978. */
  979. function getAttributeNames()
  980. {
  981. return array_keys($this->m_attribList);
  982. }
  983. /**
  984. * Gets the attribute order.
  985. *
  986. * @param string $name The name of the attribute
  987. */
  988. function getAttributeOrder($name)
  989. {
  990. return $this->m_attribIndexList[$this->m_attribList[$name]->m_index]["order"];
  991. }
  992. /**
  993. * Sets an attributes order
  994. *
  995. * @param string $name The name of the attribute
  996. * @param int $order The order of the attribute
  997. */
  998. function setAttributeOrder($name, $order)
  999. {
  1000. $this->m_attribList[$name]->m_order = $order;
  1001. $this->m_attribIndexList[$this->m_attribList[$name]->m_index]["order"] = $order;
  1002. }
  1003. /**
  1004. * Checks if the entity has a certain flag set.
  1005. * @param int $flag The flag to check.
  1006. * @return boolean True if the entity has the flag.
  1007. */
  1008. function hasFlag($flag)
  1009. {
  1010. return (($this->m_flags & $flag) == $flag);
  1011. }
  1012. /**
  1013. * Add a flag to the entity.
  1014. * @param int $flag The flag to add.
  1015. */
  1016. function addFlag($flag)
  1017. {
  1018. $this->m_flags |= $flag;
  1019. }
  1020. /**
  1021. * Removes a flag from the entity.
  1022. *
  1023. * @param int $flag The flag to remove from the attribute
  1024. */
  1025. function removeFlag($flag)
  1026. {
  1027. if ($this->hasFlag($flag))
  1028. $this->m_flags ^= $flag;
  1029. }
  1030. /**
  1031. * Returns the entity flags.
  1032. * @return Integer entity flags
  1033. */
  1034. function getFlags()
  1035. {
  1036. return $this->m_flags;
  1037. }
  1038. /**
  1039. * Set entity flags.
  1040. *
  1041. * @param int $flags entity flags
  1042. */
  1043. public function setFlags($flags)
  1044. {
  1045. $this->m_flags = $flags;
  1046. }
  1047. /**
  1048. * Returns the current partial name.
  1049. *
  1050. * @return string partial name
  1051. */
  1052. public function getPartial()
  1053. {
  1054. return $this->m_partial;
  1055. }
  1056. /**
  1057. * Is partial request?
  1058. *
  1059. * @return boolean is partial
  1060. */
  1061. function isPartial()
  1062. {
  1063. return $this->m_partial;
  1064. }
  1065. /**
  1066. * Sets the editable list attributes. If you supply this method
  1067. * with one or more string arguments, all arguments are collected in
  1068. * an array. Else the first parameter will be used.
  1069. *
  1070. * @param array $attrs list of attribute names
  1071. */
  1072. function setEditableListAttributes($attrs)
  1073. {
  1074. if (is_array($attrs))
  1075. $this->m_editableListAttributes = $attrs;
  1076. else
  1077. $this->m_editableListAttributes = func_get_args();
  1078. }
  1079. /**
  1080. * Sets the multi-record-action selection mode. Can either be
  1081. * MRA_MULTI_SELECT (default), MRA_SINGLE_SELECT or
  1082. * MRA_NO_SELECT.
  1083. *
  1084. * @param string $mode selection mode
  1085. */
  1086. function setMRASelectionMode($mode)
  1087. {
  1088. $this->m_mraSelectionMode = $mode;
  1089. }
  1090. /**
  1091. * Returns the multi-record-action selection mode.
  1092. * @return Integer multi-record-action selection mode
  1093. */
  1094. function getMRASelectionMode()
  1095. {
  1096. return $this->m_mraSelectionMode;
  1097. }
  1098. /**
  1099. * Returns the primary key sql expression of a record.
  1100. * @param array $rec The record for which the primary key is calculated.
  1101. * @return String the primary key of the record.
  1102. */
  1103. function primaryKey($rec)
  1104. {
  1105. $primKey = "";
  1106. $nrOfElements = count($this->m_primaryKey);
  1107. for ($i = 0; $i < $nrOfElements; $i++) {
  1108. $p_attrib = &$this->m_attribList[$this->m_primaryKey[$i]];
  1109. $primKey .= $this->m_table . "." . $this->m_primaryKey[$i] . "='" . $p_attrib->value2db($rec) . "'";
  1110. if ($i < ($nrOfElements - 1))
  1111. $primKey .= " AND ";
  1112. }
  1113. return $primKey;
  1114. }
  1115. /**
  1116. * Retrieve the name of the primary key attribute.
  1117. *
  1118. * Note: If an entity has a primary key that consists of multiple attributes,
  1119. * this method will retrieve only the first attribute!
  1120. * @return String First primary key attribute
  1121. */
  1122. function primaryKeyField()
  1123. {
  1124. if (count($this->m_primaryKey) === 0) {
  1125. atkwarning($this->atkEntityType() . "::primaryKeyField() called, but there are no primary key fields defined!");
  1126. return null;
  1127. }
  1128. return $this->m_primaryKey[0];
  1129. }
  1130. /**
  1131. * Returns a primary key template.
  1132. *
  1133. * Like primaryKey(), this method returns a sql expression, but in this
  1134. * case, no actual data is used. Instead, template fields are inserted
  1135. * into the expression. This is useful for rendering multiple primary
  1136. * keys later with a record and a template parser.
  1137. *
  1138. * @return String Primary key template
  1139. */
  1140. function primaryKeyTpl()
  1141. {
  1142. $primKey = "";
  1143. $nrOfElements = count($this->m_primaryKey);
  1144. for ($i = 0; $i < $nrOfElements; $i++) {
  1145. $primKey .= $this->m_primaryKey[$i] . "='[" . $this->m_primaryKey[$i] . "]'";
  1146. if ($i < ($nrOfElements - 1))
  1147. $primKey .= " AND ";
  1148. }
  1149. return $primKey;
  1150. }
  1151. /**
  1152. * Set default sort order for the entity.
  1153. * @param String $orderby Default order by. Can be an attribute name or a
  1154. * SQL expression.
  1155. */
  1156. function setOrder($orderby)
  1157. {
  1158. $this->m_default_order = $orderby;
  1159. }
  1160. /**
  1161. * Get default sort order for the entity.
  1162. * @return String $orderby Default order by. Can be an attribute name or a
  1163. * SQL expression.
  1164. */
  1165. function getOrder()
  1166. {
  1167. return $this->m_default_order;
  1168. }
  1169. /**
  1170. * Set the table that the entity should use.
  1171. *
  1172. * Note: This should be called in the constructor of derived classes,
  1173. * after the base class constructor is called.
  1174. * @param String $tablename The name of the table to use.
  1175. * @param String $seq The name of the sequence to use for autoincrement
  1176. * attributes.
  1177. * @param mixed $db The database connection to use. If ommitted, this
  1178. * defaults to the default database connection.
  1179. * So in apps using only one database, it's not necessary
  1180. * to pass this parameter.
  1181. * You can pass either a connection (atkDb instance), or
  1182. * a string containing the name of the connection to use.
  1183. */
  1184. function setTable($tablename, $seq = "", $db = NULL)
  1185. {
  1186. $this->m_table = $tablename;
  1187. if ($seq == "")
  1188. $seq = $tablename;
  1189. $this->m_seq = $seq;
  1190. $this->m_db = $db;
  1191. }
  1192. /**
  1193. * Sets the database connection.
  1194. *
  1195. * @param string|atkDb $db database name or object
  1196. */
  1197. public function setDb($db)
  1198. {
  1199. $this->m_db = $db;
  1200. }
  1201. /**
  1202. * Get the database connection for this entity.
  1203. * @return atkDb Database connection instance
  1204. */
  1205. function getDb()
  1206. {
  1207. if ($this->m_db == NULL) {
  1208. return atkGetDb();
  1209. } else if (is_object($this->m_db)) {
  1210. return $this->m_db;
  1211. } else {
  1212. // must be a named connection
  1213. return atkGetDb($this->m_db);
  1214. }
  1215. }
  1216. /**
  1217. * Create an alphabetical index.
  1218. *
  1219. * Any string- or textbased attribute can be used to create an
  1220. * alphabetical index in admin- and selectpages.
  1221. * @param String $attribname The name of the attribute for which to create
  1222. * the alphabetical index.
  1223. */
  1224. function setIndex($attribname)
  1225. {
  1226. $this->m_index = $attribname;
  1227. }
  1228. /**
  1229. * Set tab index
  1230. *
  1231. * @param string $tabname Tabname
  1232. * @param int $index Index number
  1233. * @param string $action Action name (add,edit,view)
  1234. */
  1235. function setTabIndex($tabname, $index, $action = "")
  1236. {
  1237. Adapto_Util_Debugger::debug("Adapto_Entity::setTabIndex($tabname,$index,$action)");
  1238. $actionList = array("add", "edit", "view");
  1239. if ($action != "")
  1240. $actionList = array($action);
  1241. foreach ($actionList as $action) {
  1242. $new_index = $index;
  1243. $list = &$this->m_tabList[$action];
  1244. if ($new_index < 0)
  1245. $new_index = 0;
  1246. if ($new_index > count($list))
  1247. $new_index = count($list);
  1248. $current_index = array_search($tabname, $list);
  1249. if ($current_index !== NULL) {
  1250. $tmp = array_splice($list, $current_index, 1);
  1251. array_splice($list, $new_index, 0, $tmp);
  1252. }
  1253. }
  1254. }
  1255. /**
  1256. * Set default tab being displayed in view/add/edit mode.
  1257. * After calling this method, all attributes which are added after the
  1258. * method call without specification of tab will be placed on the default
  1259. * tab. This means you should use this method before you add any
  1260. * attributes to the entity.
  1261. * If you accept the default name for the first tab ("default") you do not
  1262. * need to call this method.
  1263. * @param String $tab the name of the default tab
  1264. */
  1265. function setDefaultTab($tab = "default")
  1266. {
  1267. $this->m_default_tab = $tab;
  1268. }
  1269. /**
  1270. * Get a list of tabs for a certain action.
  1271. * @param String $action The action for which you want to retrieve the
  1272. * list of tabs.
  1273. * @return array The list of tabnames.
  1274. *
  1275. */
  1276. function getTabs($action)
  1277. {
  1278. $list = &$this->m_tabList[$action];
  1279. $disable = $this->checkTabRights($list);
  1280. $tabCode = "";
  1281. if (!is_array($list)) {
  1282. // fallback to view tabs.
  1283. $list = &$this->m_tabList["view"];
  1284. }
  1285. // Attributes can also add tabs to the tablist.
  1286. $this->m_filledTabs = array();
  1287. foreach (array_keys($this->m_attribList) as $attribname) {
  1288. $p_attrib = &$this->m_attribList[$attribname];
  1289. if ($p_attrib->hasFlag(AF_HIDE))
  1290. continue;
  1291. // attributes to which we don't have access are explicitly hidden
  1292. // Only display the attribute if the attribute
  1293. // resides on at least on visible tab
  1294. for ($i = 0, $_i = sizeof($p_attrib->m_tabs); $i < $_i; $i++) {
  1295. if ((is_array($list) && in_array($p_attrib->m_tabs[$i], $list)) || (!is_array($disable) || !in_array($p_attrib->m_tabs[$i], $disable))) {
  1296. break;
  1297. }
  1298. }
  1299. if (is_object($p_attrib)) {
  1300. $additional = $p_attrib->getAdditionalTabs($action);
  1301. if (is_array($additional) && count($additional) > 0) {
  1302. $list = Adapto_array_merge($list, $additional);
  1303. $this->m_filledTabs = Adapto_array_merge($this->m_filledTabs, $additional);
  1304. }
  1305. // Keep track of the tabs that containg attribs
  1306. // so we only display none-empty tabs
  1307. $tabCode = $this->m_attributeTabs[$attribname][0];
  1308. if (!in_array($tabCode, $this->m_filledTabs)) {
  1309. $this->m_filledTabs[] = $tabCode;
  1310. }
  1311. } else {
  1312. Adapto_Util_Debugger::debug("atkentity::getTabs() Warning: $attribname is not an object!?");
  1313. }
  1314. }
  1315. // Check if the currently known tabs all containg attributes
  1316. // so we don't end up with empty tabs
  1317. return $this->checkEmptyTabs($list);
  1318. }
  1319. /**
  1320. * Retrieve the sections for the active tab.
  1321. *
  1322. * @param String $action
  1323. * @return array The active sections.
  1324. */
  1325. function getSections($action)
  1326. {
  1327. $sections = array();
  1328. if (is_array($this->m_sectionList[$action])) {
  1329. foreach ($this->m_sectionList[$action] as $element) {
  1330. list($tab, $sec) = (strpos($element, ".") !== false) ? explode(".", $element) : array($element, null);
  1331. //if this section is on an active tab, we return it.
  1332. if ($tab == $this->getActiveTab() && $sec !== NULL)
  1333. $sections[] = $sec;
  1334. }
  1335. }
  1336. //we do not want duplicate sections on the same tab.
  1337. return array_unique($sections);
  1338. }
  1339. /**
  1340. * Add sections that must be expanded by default.
  1341. *
  1342. */
  1343. function addDefaultExpandedSections()
  1344. {
  1345. $sections = func_get_args();
  1346. $sections = $this->resolveSections($sections);
  1347. $this->m_default_expanded_sections = array_unique(array_merge($sections, $this->m_default_expanded_sections));
  1348. }
  1349. /**
  1350. * Remove sections that must be expanded by default.
  1351. *
  1352. */
  1353. function removeDefaultExpandedSections()
  1354. {
  1355. $sections = func_get_args();
  1356. $this->m_default_expanded_sections = array_diff($this->m_default_expanded_sections, $sections);
  1357. }
  1358. /**
  1359. * Check if the user has the rights to access existing tabs and
  1360. * removes tabs from the list that may not be accessed
  1361. *
  1362. * @param array $tablist Array containing the current tablist
  1363. * @return array with disable tabs
  1364. */
  1365. function checkTabRights(&$tablist)
  1366. {
  1367. global $g_entitys;
  1368. $disable = array();
  1369. if (empty($this->m_module))
  1370. return $disable;
  1371. for ($i = 0, $_i = count($tablist); $i < $_i; $i++) {
  1372. if ($tablist[$i] == "" || $tablist[$i] == "default")
  1373. continue;
  1374. $secMgr = &atkGetSecurityManager();
  1375. // load the $g_entitys array to find out what tabs are required
  1376. if (!isset($g_entitys[$this->m_module][$this->m_type])) {
  1377. include_once(Adapto_Config::getGlobal("atkroot") . "atk/atkentitytools.inc");
  1378. $module = &getModule($this->m_module);
  1379. $module->getEntitys();
  1380. }
  1381. $priv = "tab_" . $tablist[$i];
  1382. if (isset($g_entitys[$this->m_module][$this->m_type]) && Adapto_in_array($priv, $g_entitys[$this->m_module][$this->m_type])) {
  1383. // authorisation is required
  1384. if (!$secMgr->allowed($this->m_module . "." . $this->m_type, "tab_" . $tablist[$i])) {
  1385. Adapto_Util_Debugger::debug("Removing TAB " . $tablist[$i] . " because access to this tab was denied");
  1386. $disable[] = $tablist[$i];
  1387. unset($tablist[$i]);
  1388. }
  1389. }
  1390. }
  1391. if (is_array($tablist)) {
  1392. // we might have now something like:
  1393. // [0]=>tabA,[3]=>tabD
  1394. // we convert this to a 'normal' array:
  1395. // [0]=>tabA,[1]=>tabD;
  1396. $newarray = array();
  1397. foreach ($tablist as $tab)
  1398. $newarray[] = $tab;
  1399. $tablist = $newarray;
  1400. }
  1401. return $disable;
  1402. }
  1403. /**
  1404. * Remove tabs without attribs from the tablist
  1405. * @param array $list The list of tabnames
  1406. * @return array The list of tabnames without the empty tabs.
  1407. *
  1408. */
  1409. function checkEmptyTabs($list)
  1410. {
  1411. $tabList = array();
  1412. if (is_array($list)) {
  1413. foreach ($list AS $tabEntry) {
  1414. if (in_array($tabEntry, $this->m_filledTabs)) {
  1415. $tabList[] = $tabEntry;
  1416. } else {
  1417. Adapto_Util_Debugger::debug("Removing TAB " . $tabEntry . " because it had no attributes assigned");
  1418. }
  1419. }
  1420. }
  1421. return $tabList;
  1422. }
  1423. /**
  1424. * Returns the currently active tab.
  1425. *
  1426. * Note that in themes which use dhtml tabs (tabs without reloads), this
  1427. * method will always return the name of the first tab.
  1428. * @return String The name of the currently visible tab.
  1429. */
  1430. function getActiveTab()
  1431. {
  1432. global $Adapto_VARS;
  1433. $tablist = $this->getTabs($Adapto_VARS["atkaction"]);
  1434. // Note: we may not read atktab from $this->m_postvars, because $this->m_postvars is not filled if this is
  1435. // a nested entity (in a relation for example).
  1436. if (!empty($Adapto_VARS["atktab"]) && in_array($Adapto_VARS["atktab"], $tablist))
  1437. $tab = $Adapto_VARS["atktab"];
  1438. elseif (!empty($this->m_default_tab) && in_array($this->m_default_tab, $tablist))
  1439. $tab = $this->m_default_tab;
  1440. else
  1441. $tab = $tablist[0];
  1442. return $tab;
  1443. }
  1444. /**
  1445. * Get the active sections.
  1446. *
  1447. * @param string $tab The currently active tab
  1448. * @param string $mode The current mode ("edit", "add", etc.)
  1449. */
  1450. function getActiveSections($tab, $mode)
  1451. {
  1452. $activeSections = array();
  1453. if (is_array($this->m_sectionList[$mode])) {
  1454. foreach ($this->m_sectionList[$mode] as $section) {
  1455. if (substr($section, 0, strlen($tab)) == $tab) {
  1456. $sectionName = 'section_' . str_replace('.', '_', $section);
  1457. $key = array("entitytype" => $this->atkentitytype(), "section" => $sectionName);
  1458. $defaultOpen = in_array($section, $this->m_default_expanded_sections);
  1459. if (atkState::get($key, $defaultOpen ? 'opened' : 'closed') != 'closed') {
  1460. $activeSections[] = $section;
  1461. }
  1462. }
  1463. }
  1464. }
  1465. return $activeSections;
  1466. }
  1467. /**
  1468. * Add a recordset filter.
  1469. * @param String $filter The fieldname you want to filter OR a SQL where
  1470. * clause expression.
  1471. * @param String $value Required value. (Ommit this parameter if you pass
  1472. * an SQL expression for $filter.)
  1473. */
  1474. function addFilter($filter, $value = "")
  1475. {
  1476. if ($value == "") {
  1477. // $key is a where clause kind of thing
  1478. $this->m_fuzzyFilters[] = $filter;
  1479. } else {
  1480. // $key is a $key, $value is a value
  1481. $this->m_filters[$filter] = $value;
  1482. }
  1483. }
  1484. /**
  1485. * Search and remove a recordset filter.
  1486. * @param String $filter The filter to search for
  1487. * @param String $value The value to search for in case it is not a fuzzy filter
  1488. * @return TRUE if the given filter was found and removed, FALSE otherwise.
  1489. */
  1490. function removeFilter($filter, $value = "")
  1491. {
  1492. if ($value == "") {
  1493. // fuzzy
  1494. $key = array_search($filter, $this->m_fuzzyFilters);
  1495. if (is_numeric($key)) {
  1496. unset($this->m_fuzzyFilters[$key]);
  1497. $this->m_fuzzyFilters = array_values($this->m_fuzzyFilters);
  1498. return true;
  1499. }
  1500. } else {
  1501. // not fuzzy
  1502. foreach (array_keys($this->m_filters) as $key) {
  1503. if ($filter == $key && $value == $this->m_filters[$key]) {
  1504. unset($this->m_filters[$key]);
  1505. return true;
  1506. }
  1507. }
  1508. }
  1509. return false;
  1510. }
  1511. /**
  1512. * Returns the form buttons for a certain page.
  1513. *
  1514. * Can be overridden by derived classes to define custom buttons.
  1515. * @param String $mode The action for which the buttons are retrieved.
  1516. * @param array $record The record currently displayed/edited in the form.
  1517. * This param can be used to define record specific
  1518. * buttons.
  1519. * @return array
  1520. */
  1521. function getFormButtons($mode, $record)
  1522. {
  1523. $controller = Adapto_ClassLoader::getInstance('atk.atkcontroller');
  1524. $controller->setEntity($this);
  1525. return $controller->getFormButtons($mode, $record);
  1526. }
  1527. /**
  1528. * Generate a box displaying a message that the current record is locked.
  1529. * @return String The HTML fragment containing a box with the message and
  1530. * a back-button.
  1531. */
  1532. function lockPage()
  1533. {
  1534. $output = $this->statusbar();
  1535. $output .= '<img src="' . Adapto_Config::getGlobal("atkroot") . 'atk/images/lock.gif"><br><br>' . atktext("lock_locked") . '<br>';
  1536. $output .= '<br><form method="get">' . session_form(SESSION_BACK) . '<input type="submit" class="btn_cancel" value="&lt;&lt; ' . atktext('back')
  1537. . '"></form>';
  1538. $ui = &$this->getUi();
  1539. if (is_object($ui)) {
  1540. $total = $ui->renderBox(array("title" => $this->actionTitle($this->m_action), "content" => $output));
  1541. }
  1542. return $total;
  1543. }
  1544. /**
  1545. * Get the ui instance for drawing and templating purposes.
  1546. *
  1547. * @return atkUi An atkUi instance for drawing and templating.
  1548. */
  1549. function &getUi()
  1550. {
  1551. $ui = Adapto_ClassLoader::getInstance("atk.ui.atkui");
  1552. return $ui;
  1553. }
  1554. /**
  1555. * Generate a title for a certain action on a certain action.
  1556. *
  1557. * The default implementation displayes the action name, and the
  1558. * descriptor of the current record between brackets. This can be
  1559. * overridden by derived classes.
  1560. * @param String $action The action for which the title is generated.
  1561. * @param array $record The record for which the title is generated.
  1562. * @return String The full title of the action.
  1563. */
  1564. function actionTitle($action, $record = "")
  1565. {
  1566. global $g_sessionManager;
  1567. $ui = &$this->getUi();
  1568. $res = "";
  1569. if ($record != "") {
  1570. $descr = $this->descriptor($record);
  1571. $g_sessionManager->pageVar("descriptor", $descr);
  1572. }
  1573. $descriptortrace = $g_sessionManager->descriptorTrace();
  1574. $nomodule = false;
  1575. if (!empty($descriptortrace)) {
  1576. $nomodule = true;
  1577. $descrtrace = "";
  1578. // only show the last 3 elems
  1579. $cnt = count($descriptortrace);
  1580. if ($cnt > 3)
  1581. $descrtrace = "... - ";
  1582. for ($i = max(0, $cnt - 3), $_i = $cnt; $i < $_i; $i++) {
  1583. $desc = $descriptortrace[$i];
  1584. $descrtrace .= Adapto_htmlentities($desc, ENT_COMPAT) . " - ";
  1585. }
  1586. $res = $descrtrace . $res;
  1587. }
  1588. if (is_object($ui)) {
  1589. $res .= $ui->entityTitle($this, $action, $nomodule);
  1590. }
  1591. return $res;
  1592. }
  1593. /**
  1594. * Place a set of tabs around content.
  1595. * @param String $action The action for which the tabs are loaded.
  1596. * @param String $content The content that is to be displayed within the
  1597. * tabset.
  1598. * @return String The complete tabset with content.
  1599. */
  1600. function tabulate($action, $content)
  1601. {
  1602. $this->addStyle("sections.css");
  1603. $this->addStyle("tabs.css");
  1604. $list = $this->getTabs($action);
  1605. $sections = $this->getSections($action);
  1606. $tabs = count($list);
  1607. if (count($sections) > 0 || $tabs > 1) {
  1608. $page = &$this->getPage();
  1609. $page->register_script(Adapto_Config::getGlobal("atkroot") . "atk/javascript/dhtml_tabs.js.php?stateful=" . (Adapto_Config::getGlobal('dhtml_tabs_stateful') ? 1 : 0));
  1610. // Load default tab show script.
  1611. $page->register_loadscript('if ( window.showTab ) {showTab(\'' . (isset($this->m_postvars['atktab']) ? $this->m_postvars['atktab'] : '') . '\');}');
  1612. $fulltabs = $this->buildTabs($action);
  1613. $tabscript = "var tabs = new Array();\n";
  1614. foreach ($fulltabs as $tab) {
  1615. $tabscript .= "tabs[tabs.length] = '" . $tab['tab'] . "';\n";
  1616. }
  1617. $page->register_scriptcode($tabscript);
  1618. }
  1619. if ($tabs > 1) {
  1620. $ui = &$this->getUi();
  1621. if (is_object($ui)) {
  1622. return $ui->renderTabs(array("tabs" => $this->buildTabs($action), "content" => $content));
  1623. }
  1624. }
  1625. return $content;
  1626. }
  1627. /**
  1628. * Determine the default form parameters for an action template.
  1629. * @param boolean $locked If the current record is locked, pass true, so
  1630. * the lockicon can be placed in the params too.
  1631. * @return array Default form parameters for action forms (assoc. array)
  1632. */
  1633. function getDefaultActionParams($locked = false)
  1634. {
  1635. $params = $this->getHelp();
  1636. $params["lockstatus"] = $this->getLockStatusIcon($locked);
  1637. $params["formend"] = '</form>';
  1638. return $params;
  1639. }
  1640. /**
  1641. * Check attribute security.
  1642. *
  1643. * Makes some attributes read-only, or hides the attribute based
  1644. * on the current mode / record.
  1645. *
  1646. * @param string $mode current mode (add, edit, view etc.)
  1647. * @param array $record current record (optional)
  1648. */
  1649. function checkAttributeSecurity($mode, $record = NULL)
  1650. {
  1651. // check if an attribute needs to be read-only or
  1652. // even hidden based on the current record
  1653. $secMgr = &atkGetSecurityManager();
  1654. foreach (array_keys($this->m_attribList) as $attrName) {
  1655. $attr = &$this->getAttribute($attrName);
  1656. if (($mode == "add" || $mode == "edit") && !$secMgr->attribAllowed($attr, $mode, $record) && $secMgr->attribAllowed($attr, "view", $record)) {
  1657. $attr->addFlag(AF_READONLY);
  1658. } else if (!$secMgr->attribAllowed($attr, $mode, $record)) {
  1659. $attr->addFlag(AF_HIDE);
  1660. }
  1661. }
  1662. }
  1663. /**
  1664. * The preAddToEditArray method is called from within the editArray
  1665. * method prior to letting the attributes add themselves to the edit
  1666. * array, but after the edit record values have been collected (a
  1667. * combination of the current record, initial/edit values and the forced
  1668. * values). This makes it possible to do some last-minute modifications to
  1669. * the record data and possibily add some last-minute attributes etc.
  1670. *
  1671. * @param array $record the edit record
  1672. * @param string $mode edit mode (add or edit)
  1673. */
  1674. function preAddToEditArray(&$record, $mode)
  1675. {
  1676. // do nothing
  1677. }
  1678. /**
  1679. * The preAddToViewArray method is called from within the viewArray
  1680. * method prior to letting the attributes add themselves to the view
  1681. * array, but after the view record values have been collected This makes
  1682. * it possible to do some last-minute modifications to the record data
  1683. * and possibily add some last-minute attributes etc.
  1684. *
  1685. * @param array $record the edit record
  1686. * @param string $mode view mode
  1687. */
  1688. function preAddToViewArray(&$record, $mode)
  1689. {
  1690. // do nothing
  1691. }
  1692. /**
  1693. * Function outputs an array with edit fields. For each field the array
  1694. * contains the name, edit HTML code etc. (name, html, obligatory,
  1695. * error, label)
  1696. *
  1697. * @todo The editArray method should use a set of classes to build the
  1698. * form, instead of an array with an overly complex structure.
  1699. * @param String $mode The edit mode ("add" or "edit")
  1700. * @param array $record The record currently being edited.
  1701. * @param array $forceList A key-value array used to preset certain
  1702. * fields to a certain value, regardless of the
  1703. * value in the record.
  1704. * @param array $suppressList List of attributenames that you want to hide
  1705. * @param String $fieldprefix Of set, each form element is prefixed with
  1706. * the specified prefix (used in embedded form
  1707. * fields)
  1708. * @param bool $ignoreTab Ignore the tabs an attribute should be shown on.
  1709. * @param bool $injectSections Inject sections?
  1710. * @return array List of edit fields (per field ( name, html, obligatory,
  1711. * error, label })
  1712. */
  1713. function editArray($mode = "add", $record = NULL, $forceList = "", $suppressList = "", $fieldprefix = "", $ignoreTab = false, $injectSections = true)
  1714. {
  1715. // update visibility of some attributes based on the current record
  1716. $this->checkAttributeSecurity($mode, $record);
  1717. /* read metadata */
  1718. $this->setAttribSizes();
  1719. /* default values */
  1720. if (!empty($record))
  1721. $defaults = $record;
  1722. else
  1723. $defaults = array();
  1724. $result['hide'] = array();
  1725. $result['fields'] = array();
  1726. /* edit mode */
  1727. if ($mode == "edit") {
  1728. /* entitys can define edit_values */
  1729. $overrides = $this->edit_values($defaults);
  1730. foreach ($overrides as $varname => $value) {
  1731. $defaults[$varname] = $value;
  1732. }
  1733. } /* add mode */
  1734. else {
  1735. /* entitys can define initial values, if they don't already have values. */
  1736. if (!isset($defaults['atkerror'])) // only load initial values the first time (not after an error occured)
  1737. {
  1738. $overrides = $this->initial_values();
  1739. if (is_array($overrides) && count($overrides) > 0) {
  1740. foreach ($overrides as $varname => $value) {
  1741. if (!isset($defaults[$varname]) || $defaults[$varname] == "")
  1742. $defaults[$varname] = $value;
  1743. }
  1744. }
  1745. }
  1746. }
  1747. /* check for forced values */
  1748. if (is_array($forceList)) {
  1749. foreach ($forceList as $forcedvarname => $forcedvalue) {
  1750. $attribname = "";
  1751. if ($forcedvarname != "") {
  1752. if (strpos($forcedvarname, '.') > 0) {
  1753. list($firstpart, $field) = explode('.', $forcedvarname);
  1754. if ($firstpart == $this->m_table) {
  1755. // this is a filter on the current table.
  1756. $defaults[$field] = $forcedvalue;
  1757. $attribname = $field;
  1758. } else {
  1759. // this is a filter on a field of another table (something we have a
  1760. // relationship with.if(is_object($this->m_attribList[$table]))
  1761. if (is_object($this->m_attribList[$firstpart])) {
  1762. $defaults[$firstpart][$field] = $forcedvalue;
  1763. $attribname = $firstpart;
  1764. } else {
  1765. // This is not a filter for this entity.
  1766. }
  1767. }
  1768. } else {
  1769. $defaults[$forcedvarname] = $forcedvalue;
  1770. $attribname = $forcedvarname;
  1771. }
  1772. if ($attribname != "") {
  1773. if (isset($this->m_attribList[$attribname])) {
  1774. $p_attrib = &$this->m_attribList[$attribname];
  1775. if (is_object($p_attrib) && (!$p_attrib->hasFlag(AF_NO_FILTER)))
  1776. $p_attrib->m_flags |= AF_READONLY | AF_HIDE_ADD;
  1777. } else {
  1778. throw new Adapto_Exception("Attribute '$attribname' doesn't exist in the attributelist");
  1779. }
  1780. }
  1781. }
  1782. }
  1783. }
  1784. // call preAddToEditArray at the attribute level, allows attribute to do
  1785. // some last minute manipulations on for example the record
  1786. foreach ($this->getAttributes() as $attr) {
  1787. $attr->preAddToEditArray($defaults, $fieldprefix, $mode);
  1788. }
  1789. // call preAddToEditArray for the entity itself.
  1790. $this->preAddToEditArray($defaults, $mode);
  1791. // initialize dependencies
  1792. foreach ($this->getAttributes() as $attr) {
  1793. $attr->initDependencies($defaults, $fieldprefix, $mode);
  1794. }
  1795. // extra submission data
  1796. $result["hide"][] = '<input type="hidden" name="atkfieldprefix" value="' . $this->getEditFieldPrefix(false) . '">';
  1797. $result["hide"][] = '<input type="hidden" name="' . $fieldprefix . 'atkentitytype" value="' . $this->atkentitytype() . '">';
  1798. $result["hide"][] = '<input type="hidden" name="' . $fieldprefix . 'atkprimkey" value="' . atkArrayNvl($record, "atkprimkey", "") . '">';
  1799. /* For all attributes we use the edit() method to get HTML code for editting the
  1800. * attribute's data. If the attribute is hidden we use the hide() method method
  1801. * to get HTML code for hideing the attribute's data. You can override the attribute's
  1802. * edit() method by supplying an <attributename>_edit function in the derived classes.
  1803. */
  1804. $tab = $this->getActiveTab();
  1805. foreach (array_keys($this->m_attribIndexList) as $r) {
  1806. $attribname = $this->m_attribIndexList[$r]["name"];
  1807. $field = array("name" => $attribname);
  1808. $p_attrib = &$this->m_attribList[$attribname];
  1809. if ($p_attrib != NULL) {
  1810. if ($p_attrib->hasDisabledMode(DISABLED_EDIT))
  1811. continue;
  1812. $field = array("name" => $attribname);
  1813. /* fields that have not yet been initialised may be overriden in the url */
  1814. if (!array_key_exists($p_attrib->fieldName(), $defaults) && array_key_exists($p_attrib->fieldName(), $this->m_postvars)) {
  1815. $defaults[$p_attrib->fieldName()] = $this->m_postvars[$p_attrib->fieldName()];
  1816. }
  1817. /* sometimes a field is hidden although not specified by the field itself */
  1818. $theme = Adapto_ClassLoader::getInstance("Adapto_Ui_Theme");
  1819. if ($theme->getAttribute("tabtype") == "dhtml" || $ignoreTab) {
  1820. $notOnTab = FALSE;
  1821. } else {
  1822. $notOnTab = !$p_attrib->showOnTab($tab);
  1823. }
  1824. if ((is_array($suppressList) && count($suppressList) > 0 && in_array($attribname, $suppressList)) || $notOnTab) {
  1825. $p_attrib->m_flags |= ($mode == "add" ? AF_HIDE_ADD : AF_HIDE_EDIT);
  1826. }
  1827. /* we let the attribute add itself to the edit array */
  1828. $p_attrib->addToEditArray($mode, $result, $defaults, $record['atkerror'], $fieldprefix);
  1829. } else {
  1830. throw new Adapto_Exception("Attribute $attribname not found!");
  1831. }
  1832. }
  1833. if ($injectSections) {
  1834. $this->injectSections($result['fields'], $mode);
  1835. }
  1836. /* check for errors */
  1837. $result["error"] = $record['atkerror'];
  1838. /* return the result array */
  1839. return $result;
  1840. }
  1841. /**
  1842. * Function outputs an array with view fields. For each field the array
  1843. * contains the name, view HTML code etc.
  1844. *
  1845. * @todo The viewArray method should use a set of classes to build the
  1846. * form, instead of an array with an overly complex structure.
  1847. * @param String $mode The edit mode ("view")
  1848. * @param array $record The record currently being viewed.
  1849. * @param bool $injectSections Inject sections?
  1850. * @return array List of edit fields (per field ( name, html, obligatory,
  1851. * error, label })
  1852. */
  1853. function viewArray($mode, $record, $injectSections = true)
  1854. {
  1855. // update visibility of some attributes based on the current record
  1856. $this->checkAttributeSecurity($mode, $record);
  1857. // call preAddToViewArray at the attribute level, allows attribute to do
  1858. // some last minute manipulations on for example the record
  1859. foreach ($this->getAttributes() as $attr) {
  1860. $attr->preAddToViewArray($record, $mode);
  1861. }
  1862. // call preAddToViewArray for the entity itself.
  1863. $this->preAddToViewArray($record, $mode);
  1864. $tab = $this->getActiveTab();
  1865. $result = array();
  1866. foreach (array_keys($this->m_attribIndexList) as $r) {
  1867. $attribname = $this->m_attribIndexList[$r]["name"];
  1868. $p_attrib = &$this->m_attribList[$attribname];
  1869. if ($p_attrib != NULL) {
  1870. if ($p_attrib->hasDisabledMode(DISABLED_VIEW))
  1871. continue;
  1872. /* we let the attribute add itself to the view array */
  1873. $p_attrib->addToViewArray($mode, $result, $record);
  1874. } else {
  1875. throw new Adapto_Exception("Attribute $attribname not found!");
  1876. }
  1877. }
  1878. /* inject sections */
  1879. if ($injectSections) {
  1880. $this->injectSections($result['fields'], $mode);
  1881. }
  1882. /* return the result array */
  1883. return $result;
  1884. }
  1885. /**
  1886. * Add sections to the edit/view fields array.
  1887. *
  1888. * @param array $fields fields array (will be modified in-place)
  1889. */
  1890. function injectSections(&$fields)
  1891. {
  1892. $this->groupFieldsBySection($fields);
  1893. $addedSections = array();
  1894. $result = array();
  1895. foreach ($fields as $field) {
  1896. /// we add the section link before the first attribute that is in it
  1897. $fieldSections = $field['sections'];
  1898. if (!is_array($fieldSections))
  1899. $fieldSections = array($fieldSections);
  1900. $newSections = array_diff($fieldSections, $addedSections);
  1901. if (count($newSections) > 0) {
  1902. foreach ($newSections as $section) {
  1903. if (strpos($section, '.') !== FALSE) {
  1904. $result[] = array("html" => "section", "name" => $section, "tabs" => $field['tabs']);
  1905. $addedSections[] = $section;
  1906. }
  1907. }
  1908. }
  1909. $result[] = $field;
  1910. }
  1911. $fields = $result;
  1912. }
  1913. /**
  1914. * Group fields by section.
  1915. *
  1916. * @param array $fields fields array (will be modified in-place)
  1917. */
  1918. function groupFieldsBySection(&$fields)
  1919. {
  1920. $result = array();
  1921. $sections = array();
  1922. // first find sectionless fields and collect all sections
  1923. foreach ($fields as $field) {
  1924. if ($field["sections"] == "*" || (count($field["sections"]) == 1 && $field["sections"][0] == $this->m_default_tab)) {
  1925. $result[] = $field;
  1926. } else if (is_array($field['sections'])) {
  1927. $sections = array_merge($sections, $field['sections']);
  1928. }
  1929. }
  1930. $sections = array_unique($sections);
  1931. // loop through each section (except the default tab/section) of the mode we are currently in.
  1932. while (count($sections) > 0) {
  1933. $section = array_shift($sections);
  1934. // find fields for this section
  1935. foreach ($fields as $field) {
  1936. if (is_array($field["sections"]) && in_array($section, $field["sections"]))
  1937. $result[] = $field;
  1938. }
  1939. }
  1940. $fields = $result;
  1941. }
  1942. /**
  1943. * Retrieve the initial values for a new record.
  1944. *
  1945. * The system calls this method to create a new record. By default
  1946. * this method returns an empty record, but derived entitys may override
  1947. * this method to perform record initialization.
  1948. *
  1949. * @return array Array containing an initial value per attribute.
  1950. * Only attributes that are initialized appear in the
  1951. * array.
  1952. */
  1953. function initial_values()
  1954. {
  1955. $record = array();
  1956. foreach (array_keys($this->m_attribList) as $attrName) {
  1957. $attr = &$this->getAttribute($attrName);
  1958. if (is_array($this->m_postvars) && isset($this->m_postvars[$attrName])) {
  1959. $value = $attr->fetchValue($this->m_postvars);
  1960. } else {
  1961. $value = $attr->initialValue();
  1962. }
  1963. if ($value !== NULL)
  1964. $record[$attr->fieldName()] = $value;
  1965. }
  1966. return $record;
  1967. }
  1968. /**
  1969. * Retrieve new values for an existing record.
  1970. *
  1971. * The system calls this method to override the values of a record
  1972. * before editing the record.
  1973. * The default implementation does not do anything to the record, but
  1974. * derived classes may override this method to make modifications to.
  1975. * the record.
  1976. *
  1977. * @param array $record The record that is about to be edited.
  1978. * @return array The manipulated record.
  1979. */
  1980. function edit_values($record)
  1981. {
  1982. return $record;
  1983. }
  1984. /**
  1985. * Get the template to use for a certain action.
  1986. *
  1987. * The system calls this method to determine which template to use when
  1988. * rendering a certain screen. The default implementation always returns
  1989. * the same template for the same action (it ignores parameter 2 and 3).
  1990. * You can override this method in derived classes however, to determine
  1991. * on the fly which template to use.
  1992. * The action, the current record (if any) and the tab are passed as
  1993. * parameter. By using these params, you can have custom templates per
  1994. * action, and/or per tab, and even per record.
  1995. *
  1996. * @param String $action The action for which you wnat to retrieve the
  1997. * template.
  1998. * @param array $record The record for which you want to return the
  1999. * template (or NULL if there is no record).
  2000. * @param String $tab The name of the tab for which you want to
  2001. * retrieve the template.
  2002. * @return String The filename of the template (without path)
  2003. */
  2004. function getTemplate($action, $record = NULL, $tab = "")
  2005. {
  2006. switch ($action) {
  2007. case "add": // add and edit both use the same form.
  2008. case "edit":
  2009. return "editform.tpl";
  2010. case "view":
  2011. return "viewform.tpl";
  2012. case "search":
  2013. return "searchform.tpl";
  2014. case "smartsearch":
  2015. return "smartsearchform.tpl";
  2016. case "admin":
  2017. return "recordlist.tpl";
  2018. }
  2019. }
  2020. /**
  2021. * Function outputs a form with all values hidden.
  2022. *
  2023. * This is probably only useful for the atkOneToOneRelation's hide method.
  2024. *
  2025. * @param String $mode The edit mode ("add" or "edit")
  2026. * @param array $record The record that should be hidden.
  2027. * @param array $forceList A key-value array used to preset certain
  2028. * fields to a certain value, regardless of the
  2029. * value in the record.
  2030. * @param String $fieldprefix Of set, each form element is prefixed with
  2031. * the specified prefix (used in embedded form
  2032. * fields)
  2033. * @return String HTML fragment containing all hidden elements.
  2034. *
  2035. */
  2036. function hideForm($mode = "add", $record = NULL, $forceList = "", $fieldprefix = "")
  2037. {
  2038. /* suppress all */
  2039. $suppressList = array();
  2040. foreach (array_keys($this->m_attribIndexList) as $r)
  2041. $suppressList[] = $this->m_attribIndexList[$r]["name"];
  2042. /* get data, transform into "form", return */
  2043. $data = $this->editArray($mode, $record, $forceList, $suppressList, $fieldprefix);
  2044. foreach ($data["hide"] as $hide)
  2045. $form .= $hide;
  2046. return $form;
  2047. }
  2048. /**
  2049. * Builds a list of tabs.
  2050. *
  2051. * This doesn't generate the actual HTML code, but returns the data for
  2052. * the tabs (title, selected, urls that should be loaded upon click of the
  2053. * tab etc).
  2054. * @param String $action The action for which the tabs should be generated.
  2055. * @return array List of tabs
  2056. * @todo Make translation of tabs module aware
  2057. */
  2058. function buildTabs($action = "")
  2059. {
  2060. if ($action == "") {
  2061. // assume active action
  2062. $action = $this->m_action;
  2063. }
  2064. $result = array();
  2065. // which tab is currently selected
  2066. $tab = $this->getActiveTab();
  2067. // build navigator
  2068. $list = &$this->getTabs($action);
  2069. if (is_array($list)) {
  2070. $newtab["total"] = count($list);
  2071. foreach ($list as $t) {
  2072. $newtab["title"] = $this->text(array("tab_$t", $t));
  2073. $newtab["tab"] = $t;
  2074. $url = atkSelf() . "?atkentitytype=" . $this->atkEntityType() . "&atkaction=" . $this->m_action . "&atktab=" . $t;
  2075. if ($this->m_action == "view") {
  2076. $newtab["link"] = atkSessionManager::sessionUrl($url, SESSION_DEFAULT);
  2077. } else {
  2078. $newtab["link"] = "javascript:atkSubmit('" . atkurlencode(atkSessionManager::sessionUrl($url, SESSION_DEFAULT)) . "')";
  2079. }
  2080. $newtab["selected"] = ($t == $tab);
  2081. $result[] = $newtab;
  2082. }
  2083. }
  2084. return $result;
  2085. }
  2086. /**
  2087. * Retrieve an array with the default actions for a certain mode.
  2088. *
  2089. * This will return a list of actions that can be performed on records
  2090. * of this entity in an admin screen.
  2091. * The actions may contain a [pk] template variable to reference a record,
  2092. * so for each record you should run the stringparser on the action.
  2093. *
  2094. * @param String $mode The mode for which you want a list of actions.
  2095. * Currently available modes for this method:
  2096. * - "admin" (for actions in adminscreens)
  2097. * - "relation" (for the list of actions when
  2098. * displaying a recordlist in a onetomany-relation)
  2099. * - "view" (for actions when viewing only)
  2100. * Note: the default implementation of defaultActions
  2101. * makes no difference between "relation" and "admin"
  2102. * and will return the same actions for both, but you
  2103. * might want to override this behaviour in derived
  2104. * classes.
  2105. * @param array $params An array of extra parameters to add to all the
  2106. * action urls. You can use this to pass things like
  2107. * an atkfilter for example. The array should be
  2108. * key/value based.
  2109. * @return array List of actions in the form array($action=>$actionurl)
  2110. */
  2111. function defaultActions($mode, $params = array())
  2112. {
  2113. $actions = array();
  2114. $postfix = "";
  2115. if (count($params) > 0) {
  2116. foreach ($params as $key => $value) {
  2117. $postfix .= "&$key=" . rawurlencode($value);
  2118. }
  2119. }
  2120. // Changed: it used to be that you could only view if you didn't have
  2121. // edit right. This was changed because of Achievo bug #41
  2122. // (http://www.achievo.org/bug/41)
  2123. $actionbase = atkSelf() . '?atkentitytype=' . $this->atkentitytype() . '&atkselector=[pk]' . $postfix;
  2124. if (!$this->hasFlag(EF_NO_VIEW) && $this->allowed("view")) {
  2125. $actions["view"] = $actionbase . '&atkaction=view';
  2126. }
  2127. if ($mode != "view") {
  2128. if (!$this->hasFlag(EF_NO_EDIT) && $this->allowed("edit")) {
  2129. $actions["edit"] = $actionbase . '&atkaction=edit';
  2130. }
  2131. if (!$this->hasFlag(EF_NO_DELETE) && $this->allowed("delete")) {
  2132. $actions["delete"] = $actionbase . '&atkaction=delete';
  2133. }
  2134. if ($this->hasFlag(EF_COPY) && $this->allowed("copy")) {
  2135. $actions["copy"] = $actionbase . '&atkaction=copy';
  2136. }
  2137. if ($this->hasFlag(EF_EDITAFTERCOPY) && $this->allowed("editcopy")) {
  2138. $actions["editcopy"] = $actionbase . '&atkaction=editcopy';
  2139. }
  2140. }
  2141. return $actions;
  2142. }
  2143. /**
  2144. * Sets the priority range, for multi-record-priority actions.
  2145. * @param int $min the minimum priority
  2146. * @param int $max the maximum priority (0 for auto => min + record count)
  2147. */
  2148. function setPriorityRange($min = 1, $max = 0)
  2149. {
  2150. $this->m_priority_min = (int) $min;
  2151. if ($max < $this->m_priority_min)
  2152. $max = 0;
  2153. else
  2154. $this->m_priority_max = $max;
  2155. }
  2156. /**
  2157. * Sets the possible multi-record-priority actions.
  2158. * @param array $actions list of actions
  2159. */
  2160. function setPriorityActions($actions)
  2161. {
  2162. if (!is_array($actions))
  2163. $this->m_priority_actions = array();
  2164. else
  2165. $this->m_priority_actions = $actions;
  2166. }
  2167. /**
  2168. * Get extended search action.
  2169. *
  2170. * @return extended search action
  2171. */
  2172. function getExtendedSearchAction()
  2173. {
  2174. if (empty($this->m_extended_search_action))
  2175. return Adapto_Config::getGlobal('extended_search_action');
  2176. else
  2177. return $this->m_extended_search_action;
  2178. }
  2179. /**
  2180. * Set extended search action.
  2181. *
  2182. * @param string $action extended search action
  2183. */
  2184. function setExtendedSearchAction($action)
  2185. {
  2186. $this->m_extended_search_action = $action;
  2187. }
  2188. /**
  2189. * Function returns a page in which the user is asked if he really wants
  2190. * to perform a certain action.
  2191. * @param mixed $atkselector Selector of current record on which the
  2192. * action will be performed (String), or an
  2193. * array of selectors when multiple records are
  2194. * processed at once. The method uses the
  2195. * selector(s) to display the current record(s)
  2196. * in the confirmation page.
  2197. * @param String $action The action for which confirmation is needed.
  2198. * @param boolean $locked Pass true if the current record is locked.
  2199. * @param boolean $checkoverride If set to true, this method will try to
  2200. * find a custom method named
  2201. * "confirm".$action."()" (e.g.
  2202. * confirmDelete() and call that method
  2203. * instead.
  2204. * @param boolean $mergeSelectors Merge all selectors to one selector string (if more then one)?
  2205. *
  2206. * @return String Complete html fragment containing a box with the
  2207. * confirmation page, or the output of the custom
  2208. * override if $checkoverride was true.
  2209. */
  2210. function confirmAction($atkselector, $action, $locked = false, $checkoverride = true, $mergeSelectors = true, $csrfToken = null)
  2211. {
  2212. $method = 'confirm' . $action;
  2213. if ($checkoverride && method_exists($this, $method))
  2214. return $this->$method($atkselector, $locked);
  2215. $ui = &$this->getUi();
  2216. $this->addStyle("style.css");
  2217. if (is_array($atkselector))
  2218. $atkselector_str = '((' . implode($atkselector, ') OR (') . '))';
  2219. else
  2220. $atkselector_str = $atkselector;
  2221. $formstart = '<form action="' . atkSelf() . '?"' . SID . ' method="post">';
  2222. $formstart .= session_form();
  2223. $formstart .= '<input type="hidden" name="atkaction" value="' . $action . '">';
  2224. $formstart .= '<input type="hidden" name="atkentitytype" value="' . $this->atkentitytype() . '">';
  2225. if (isset($csrfToken)) {
  2226. $this->getHandler($action);
  2227. $formstart .= '<input type="hidden" name="atkcsrftoken" value="' . $csrfToken . '">';
  2228. }
  2229. if ($mergeSelectors) {
  2230. $formstart .= '<input type="hidden" name="atkselector" value="' . $atkselector_str . '">';
  2231. } else if (!is_array($atkselector)) {
  2232. $formstart .= '<input type="hidden" name="atkselector" value="' . $atkselector . '">';
  2233. } else {
  2234. foreach ($atkselector as $selector)
  2235. $formstart .= '<input type="hidden" name="atkselector[]" value="' . $selector . '">';
  2236. }
  2237. $buttons = $this->getFormButtons($action, array());
  2238. if (count($buttons) == 0) {
  2239. $buttons[] = '<input name="confirm" type="submit" class="btn_ok atkdefaultbutton" value="' . $this->text('yes') . '">';
  2240. $buttons[] = '<input name="cancel" type="submit" class="btn_cancel" value="' . $this->text('no') . '">';
  2241. }
  2242. $content = "";
  2243. $recs = $this->selectDb($atkselector_str, "", "", "", $this->descriptorFields());
  2244. if (count($recs) == 1) {
  2245. // 1 record, put it in the page title (with the actionTitle call, a few lines below)
  2246. $record = $recs[0];
  2247. $this->getPage()->setTitle(atktext('app_shorttitle') . " - " . $this->actionTitle($action, $record));
  2248. } else {
  2249. // we are gonna perform an action on more than one record
  2250. // show a list of affected records, at least if we can find a
  2251. // descriptor_def method
  2252. if ($this->m_descTemplate != NULL || method_exists($this, "descriptor_def")) {
  2253. $record = "";
  2254. $content .= "<ul>";
  2255. for ($i = 0, $_i = count($recs); $i < $_i; $i++) {
  2256. $content .= "<li>" . str_replace(' ', '&nbsp;', Adapto_htmlentities($this->descriptor($recs[$i])));
  2257. }
  2258. $content .= "</ul>";
  2259. }
  2260. }
  2261. $content .= '<br>' . $this->confirmActionText($atkselector, $action, true);
  2262. $output = $ui
  2263. ->renderAction($action, array("content" => $content, "formstart" => $formstart, "formend" => '</form>', "buttons" => $buttons));
  2264. return $ui->renderBox(array("title" => $this->actionTitle($action, $record), "content" => $output));
  2265. }
  2266. /**
  2267. * Determine the confirmation message.
  2268. * @param String $atkselector The record(s) on which the action is
  2269. * performed.
  2270. * @param String $action The action being performed.
  2271. * @param boolean $checkoverride If true, returns the output of a custom
  2272. * method named "confirm".$action."text()"
  2273. * @return String The confirmation text.
  2274. */
  2275. function confirmActionText($atkselector = "", $action = "delete", $checkoverride = TRUE)
  2276. {
  2277. $method = 'confirm' . $action . 'text';
  2278. if ($checkoverride && method_exists($this, $method))
  2279. return $this->$method($atkselector);
  2280. else
  2281. return $this->text("confirm_$action" . (is_array($atkselector) && count($atkselector) > 1 ? '_multi' : ''));
  2282. }
  2283. /**
  2284. * Small compare function for sorting attribs on order field
  2285. * @access private
  2286. * @param array $a The first attribute
  2287. * @param array $b The second attribute
  2288. * @return int
  2289. */
  2290. function attrib_cmp($a, $b)
  2291. {
  2292. if ($a["order"] == $b["order"])
  2293. return 0;
  2294. return ($a["order"] < $b["order"]) ? -1 : 1;
  2295. }
  2296. /**
  2297. * This function initialises certain elements of the entity.
  2298. *
  2299. * This must be called right after the constructor. The function has a
  2300. * check to prevent it from being executed twice. If you construct an entity
  2301. * using 'new', you have to call this method. If you construct it with the
  2302. * getEntity or newEntity method, you don't have to call this method.
  2303. */
  2304. function init()
  2305. {
  2306. Adapto_Util_Debugger::debug("init for " . $this->m_type);
  2307. global $g_modifiers;
  2308. // Check if initialisation is not already done.
  2309. if ($this->m_initialised == true)
  2310. return;
  2311. // We assign the $this reference to the attributes at this stage, since
  2312. // it fails when we do it in the add() function.
  2313. // See also the comments in the add() function.
  2314. foreach (array_keys($this->m_attribList) as $attribname) {
  2315. $p_attrib = &$this->m_attribList[$attribname];
  2316. $p_attrib->setOwnerInstance($this);
  2317. }
  2318. // See if there are modules active that modify this entity, and apply the
  2319. // modifiers if found.
  2320. if (isset($g_modifiers[$this->atkentitytype()])) {
  2321. foreach ($g_modifiers[$this->atkentitytype()] as $modulename) {
  2322. $module = &getModule($modulename);
  2323. $module->modifier($this);
  2324. }
  2325. }
  2326. $this->_addListeners();
  2327. // We set the tabs for the attributes
  2328. foreach (array_keys($this->m_attribList) as $attribname) {
  2329. $p_attrib = &$this->m_attribList[$attribname];
  2330. $p_attrib->setTabs($this->m_attributeTabs[$attribname]);
  2331. }
  2332. $this->attribSort();
  2333. $lockType = Adapto_Config::getGlobal("lock_type");
  2334. if (!empty($lockType) && $this->hasFlag(EF_LOCK)) {
  2335. $this->m_lock = Adapto_ClassLoader::getInstance("atk.lock.atklock");
  2336. } else
  2337. $this->removeFlag(EF_LOCK);
  2338. $this->m_defaultlanguage = strtoupper(Adapto_Config::getGlobal("defaultlanguage"));
  2339. $this->m_initialised = true;
  2340. // Call the attributes postInit method to doe some last time
  2341. // initialization if necessary.
  2342. foreach (array_keys($this->m_attribList) as $attribname) {
  2343. $p_attrib = &$this->m_attribList[$attribname];
  2344. $p_attrib->postInit();
  2345. }
  2346. }
  2347. /**
  2348. * Add the listeners for the current entity
  2349. * A listener can be defined either by placing an instantiated object
  2350. * or the full location in
  2351. * called $g_entityListeners (useful for example for adding listeners
  2352. * to entitys from another module's module.inc file. in module.inc files,
  2353. * $listeners can be used to add listeners to an entity.
  2354. * @access private
  2355. */
  2356. function _addListeners()
  2357. {
  2358. global $g_entityListeners;
  2359. if (isset($g_entityListeners[$this->atkentitytype()])) {
  2360. foreach ($g_entityListeners[$this->atkentitytype()] as $listener) {
  2361. if (is_object($listener)) {
  2362. $this->addListener($listener);
  2363. } else {
  2364. if (is_string($listener)) {
  2365. $listenerobj = &Adapto_ClassLoader::create($listener);
  2366. if (is_object($listenerobj)) {
  2367. $this->addListener($listenerobj);
  2368. } else
  2369. Adapto_Util_Debugger::debug("We couldn't find a classname for listener with supposed entitytype: '$listener'");
  2370. } else {
  2371. Adapto_Util_Debugger::debug("Failed to add listener with supposed entitytype: '$listener'");
  2372. }
  2373. }
  2374. }
  2375. }
  2376. }
  2377. /**
  2378. * This function reads meta information from the database and initialises
  2379. * its attributes with the metadata.
  2380. *
  2381. * This method should be called before rendering a form, if you want the
  2382. * sizes of all the inputs to match the fieldlengths from the database.
  2383. */
  2384. function setAttribSizes()
  2385. {
  2386. if ($this->m_attribsizesset)
  2387. return true;
  2388. $db = &$this->getDb();
  2389. $metainfo = $db->tableMeta($this->m_table);
  2390. foreach (array_keys($this->m_attribList) as $attribname) {
  2391. $p_attrib = &$this->m_attribList[$attribname];
  2392. if (is_object($p_attrib)) {
  2393. $p_attrib->fetchMeta($metainfo);
  2394. }
  2395. }
  2396. $this->m_attribsizesset = true;
  2397. }
  2398. /**
  2399. * This is the wrapper method for all http requests on an entity.
  2400. *
  2401. * The method looks at the atkaction from the postvars and determines what
  2402. * should be done. If possible, it instantiates actionHandlers for
  2403. * handling the actual action.
  2404. *
  2405. * @param array $postvars The request variables for the entity.
  2406. * @param int $flags Render flags (see class atkPage).
  2407. */
  2408. function dispatch($postvars, $flags = NULL)
  2409. {
  2410. Adapto_Util_Debugger::debug("Adapto_Entity::dispatch()");
  2411. $controller = &atkcontroller::getInstance();
  2412. $controller->setEntity($this);
  2413. return $controller->handleRequest($postvars, $flags);
  2414. }
  2415. /**
  2416. * Render a generic page, with a box, title, stacktrace etc.
  2417. * @param String $title The pagetitle and if $content is a string, also
  2418. * the boxtitle.
  2419. * @param mixed $content The content to display on the page. This can be:
  2420. * - A string which will be the content of a single
  2421. * box on the page.
  2422. * - An associative array of $boxtitle=>$boxcontent
  2423. * pairs. Each pair will be rendered as a seperate
  2424. * box.
  2425. * @return String A complete html page with the desired content.
  2426. */
  2427. function genericPage($title, $content)
  2428. {
  2429. $controller = &atkcontroller::getInstance();
  2430. $controller->setEntity($this);
  2431. return $controller->genericPage($title, $content);
  2432. }
  2433. /**
  2434. * Render a generic action.
  2435. *
  2436. * Renders actionpage.tpl for the desired action. This includes the
  2437. * given block(s) and a pagetrial, but not a box.
  2438. * @param String $action The action for which the page is rendered.
  2439. * @param mixed $blocks Pieces of html content to be rendered. Can be a
  2440. * single string with content, or an array with
  2441. * multiple content blocks.
  2442. * @return String Piece of HTML containing the given blocks and a pagetrail.
  2443. */
  2444. function renderActionPage($action, $blocks = array())
  2445. {
  2446. $controller = &atkcontroller::getInstance();
  2447. $controller->setEntity($this);
  2448. return $controller->renderActionPage($action, $blocks);
  2449. }
  2450. /**
  2451. * Use this function to enable feedback for one or more actions.
  2452. *
  2453. * When feedback is enabled, the action does not immediately return to the
  2454. * previous screen, but first displays a message to the user. (e.g. 'The
  2455. * record has been saved').
  2456. *
  2457. * @param mixed $action The action for which feedback is enabled. You can
  2458. * either pass one action or an array of actions.
  2459. * @param int $statusmask The status(ses) for which feedback is enabled.
  2460. * If for example this is set to ACTION_FAILED,
  2461. * feedback is enabled only when the specified
  2462. * action failed. It is possible to specify more
  2463. * than one status by concatenating with '|'.
  2464. */
  2465. function setFeedback($action, $statusmask)
  2466. {
  2467. if (is_array($action)) {
  2468. for ($i = 0, $_i = count($action); $i < $_i; $i++) {
  2469. $this->m_feedback[$action[$i]] = $statusmask;
  2470. }
  2471. } else {
  2472. $this->m_feedback[$action] = $statusmask;
  2473. }
  2474. }
  2475. /**
  2476. * Get the page instance of the page on which the entity can render output.
  2477. * @return atkPage The page instance.
  2478. */
  2479. function &getPage()
  2480. {
  2481. $page = Adapto_ClassLoader::getInstance("atk.ui.atkpage");
  2482. return $page;
  2483. }
  2484. /**
  2485. * Returns a new page builder instance.
  2486. *
  2487. * @return atkPageBuilder
  2488. */
  2489. public function createPageBuilder()
  2490. {
  2491. return new Adapto_PageBuilder($this);
  2492. }
  2493. /**
  2494. * Redirect the browser to a different location.
  2495. *
  2496. * This is usually used at the end of actions that have no output. An
  2497. * example: when the user clicks 'save and close' in an edit screen, the
  2498. * action 'save' is executed. If the save is succesful, this method is
  2499. * called to redirect the user back to the adminpage.
  2500. * When $config_debug is set to 2, redirects are paused and you can click
  2501. * a link to execute the redirect (useful for debugging the action that
  2502. * called the redirect).
  2503. * Note: this method should be called before any output has been sent to
  2504. * the browser, i.e. before any echo or before the call to
  2505. * atkOutput::outputFlush().
  2506. *
  2507. * @static
  2508. * @param String $location The url to which you want to redirect the user.
  2509. * If ommitted, the call automatically redirects
  2510. * to the previous screen of the user. (one level
  2511. * back on the session stack).
  2512. * @param array $recordOrExit If you pass a record here, the record is passed
  2513. * as 'atkpkret' to the redirected url. Usually it's
  2514. * not necessary to pass this parameter. If you pass a
  2515. * boolean here we assume it's value must be used for
  2516. * the exit parameter.
  2517. * @param boolean $exit Exit script after redirect.
  2518. * @param int $levelskip Number of levels to skip
  2519. */
  2520. function redirect($location = "", $recordOrExit = array(), $exit = false, $levelskip = 1)
  2521. {
  2522. global $g_returnurl;
  2523. Adapto_Util_Debugger::debug("atkentity::redirect()");
  2524. $record = $recordOrExit;
  2525. if (is_bool($recordOrExit)) {
  2526. $record = array();
  2527. $exit = $recordOrExit;
  2528. }
  2529. if ($g_returnurl != "")
  2530. $location = $g_returnurl;
  2531. if ($location == "") {
  2532. $location = atkSessionManager::sessionUrl(atkSelf(), SESSION_BACK, $levelskip);
  2533. }
  2534. if (count($record)) {
  2535. if (isset($this->m_postvars["atkpkret"])) {
  2536. $location .= "&" . $this->m_postvars["atkpkret"] . "=" . rawurlencode($this->primaryKey($record));
  2537. }
  2538. }
  2539. // The actual redirect.
  2540. if (Adapto_Config::getGlobal("debug") >= 2) {
  2541. $debugger = Adapto_ClassLoader::getInstance('atk.utils.Adapto_Util_Debugger::debugger');
  2542. $debugger->setRedirectUrl($location);
  2543. Adapto_Util_Debugger::debug('Non-debug version would have redirected to <a href="' . $location . '">' . $location . '</a>');
  2544. if ($exit) {
  2545. $output = &atkOutput::getInstance();
  2546. $output->outputFlush();
  2547. exit();
  2548. }
  2549. } else {
  2550. Adapto_Util_Debugger::debug('redirecting to: ' . $location);
  2551. if (substr($location, -1) == "&") {
  2552. $location = substr($location, 0, -1);
  2553. }
  2554. if (substr($location, -1) == "?") {
  2555. $location = substr($location, 0, -1);
  2556. }
  2557. global $g_error_msg;
  2558. if (count($g_error_msg) > 0) {
  2559. mailreport();
  2560. }
  2561. header('Location: ' . $location);
  2562. if ($exit) {
  2563. exit();
  2564. }
  2565. }
  2566. }
  2567. /**
  2568. * Parse a set of url vars into a valid record structure.
  2569. *
  2570. * When attributes are posted in a formposting, the values may not be
  2571. * valid yet. After posting, a call to updateRecord should be made to
  2572. * translate the html values into the internal values that the attributes
  2573. * work with.
  2574. * @param array $vars The request variables that were posted from a form.
  2575. * @param array $includes Only fetch the value for these attributes.
  2576. * @param array $excludes Don't fetch the value for these attributes.
  2577. * @param array $postedOnly Only fetch the value for attributes that have really been posted.
  2578. * @return array A valid record.
  2579. */
  2580. function updateRecord($vars = "", $includes = NULL, $excludes = NULL, $postedOnly = false)
  2581. {
  2582. if ($vars == "")
  2583. $vars = $this->m_postvars;
  2584. $record = array();
  2585. foreach (array_keys($this->m_attribList) as $attribname) {
  2586. if ((!is_array($includes) || in_array($attribname, $includes)) && (!is_array($excludes) || !in_array($attribname, $excludes))) {
  2587. $p_attrib = &$this->m_attribList[$attribname];
  2588. if (!$postedOnly || $p_attrib->isPosted($vars)) {
  2589. $record[$p_attrib->fieldName()] = $p_attrib->fetchValue($vars);
  2590. }
  2591. }
  2592. }
  2593. if (isset($vars['atkprimkey']))
  2594. $record["atkprimkey"] = $vars["atkprimkey"];
  2595. return $record;
  2596. }
  2597. /**
  2598. * Update a record with variables from a form posting.
  2599. *
  2600. * Similar to updateRecord(), but here you can pass an existing record
  2601. * (for example loaded from the db), and update it with the the variables
  2602. * from the request. Instead of returning a record, the record you pass
  2603. * is modified directly.
  2604. *
  2605. * @param array $record The record to update.
  2606. * @param array $vars The request variables that were posted from a form.
  2607. */
  2608. function modifyRecord(&$record, $vars)
  2609. {
  2610. foreach (array_keys($this->m_attribList) as $attribname) {
  2611. $p_attrib = &$this->m_attribList[$attribname];
  2612. $record[$p_attrib->fieldName()] = $p_attrib->fetchValue($vars);
  2613. }
  2614. }
  2615. /**
  2616. * Get descriptor handler.
  2617. * @return Object descriptor handler
  2618. */
  2619. function &getDescriptorHandler()
  2620. {
  2621. return $this->m_descHandler;
  2622. }
  2623. /**
  2624. * Set descriptor handler.
  2625. * @param Object $handler The descriptor handler.
  2626. */
  2627. function setDescriptorHandler(&$handler)
  2628. {
  2629. $this->m_descHandler = &$handler;
  2630. }
  2631. /**
  2632. * Returns the descriptor template for this entity.
  2633. * @return String The descriptor Template
  2634. */
  2635. function getDescriptorTemplate()
  2636. {
  2637. return $this->m_descTemplate;
  2638. }
  2639. /**
  2640. * Sets the descriptor template for this entity.
  2641. * @param String $template The descriptor template.
  2642. */
  2643. function setDescriptorTemplate($template)
  2644. {
  2645. $this->m_descTemplate = $template;
  2646. }
  2647. /**
  2648. * Retrieve the list of attributes that are used in the descriptor
  2649. * definition.
  2650. * @return array The names of the attributes forming the descriptor.
  2651. */
  2652. function descriptorFields()
  2653. {
  2654. $fields = array();
  2655. // See if entity has a custom descriptor definition.
  2656. if ($this->m_descTemplate != NULL || method_exists($this, "descriptor_def")) {
  2657. if ($this->m_descTemplate != NULL)
  2658. $descriptordef = $this->m_descTemplate;
  2659. else
  2660. $descriptordef = $this->descriptor_def();
  2661. // parse fields from descriptordef
  2662. $parser = new Adapto_StringParser($descriptordef);
  2663. $fields = $parser->getFields();
  2664. // There might be fields that have a '.' in them. These fields are
  2665. // a concatenation of an attributename (probably a relation), and a subfield
  2666. // (a field of the destination entity).
  2667. // The actual field is the one in front of the '.'.
  2668. for ($i = 0, $_i = count($fields); $i < $_i; $i++) {
  2669. $elems = explode(".", $fields[$i]);
  2670. if (count($elems) > 1) {
  2671. // dot found. attribute is the first item.
  2672. $fields[$i] = $elems[0];
  2673. }
  2674. }
  2675. } else {
  2676. // default descriptor.. (default is first attribute of an entity)
  2677. $keys = array_keys($this->m_attribList);
  2678. $fields[0] = $keys[0];
  2679. }
  2680. return $fields;
  2681. }
  2682. /**
  2683. * Determine a descriptor of a record.
  2684. *
  2685. * The descriptor is a string that describes a record for the user. For
  2686. * person records, this may be the firstname and the lastname, for
  2687. * companies it may be the company name plus the city etc.
  2688. * The descriptor is used when displaying records in a dropdown for
  2689. * example, or in the title of editpages, delete confirmations etc.
  2690. *
  2691. * The descriptor method calls a method named descriptor_def() on the entity
  2692. * to retrieve a template for the descriptor (string with attributenames
  2693. * between blockquotes, for example "[lastname], [firstname]".
  2694. *
  2695. * If the entity has no descriptor_def() method, the first attribute of the
  2696. * entity is used as descriptor.
  2697. *
  2698. * Derived classes may override this method to implement custom descriptor
  2699. * logic.
  2700. *
  2701. * @param array $rec The record for which the descriptor is returned.
  2702. * @return String The descriptor for the record.
  2703. */
  2704. function descriptor($rec = "")
  2705. {
  2706. // Descriptor handler is set?
  2707. if ($this->m_descHandler != NULL)
  2708. return $this->m_descHandler->descriptor($rec, $this);
  2709. // Descriptor template is set?
  2710. if ($this->m_descTemplate != NULL) {
  2711. $parser = new Adapto_StringParser($this->m_descTemplate);
  2712. return $parser->parse($rec);
  2713. }
  2714. // See if entity has a custom descriptor definition.
  2715. else if (method_exists($this, "descriptor_def")) {
  2716. $parser = new Adapto_StringParser($this->descriptor_def());
  2717. return $parser->parse($rec);
  2718. } else {
  2719. // default descriptor.. (default is first attribute of an entity)
  2720. $keys = array_keys($this->m_attribList);
  2721. return $rec[$keys[0]];
  2722. }
  2723. }
  2724. /**
  2725. * Sets the lock mode.
  2726. *
  2727. * @param int $lockMode lock mode (atkLock::EXCLUSIVE, atkLock::SHARED)
  2728. */
  2729. public function setLockMode($lockMode)
  2730. {
  2731. $this->m_lockMode = $lockMode;
  2732. }
  2733. /**
  2734. * Returns the lock mode.
  2735. *
  2736. * @return int lock mode (atkLock::EXCLUSIVE, atkLock::SHARED)
  2737. */
  2738. public function getLockMode()
  2739. {
  2740. return $this->m_lockMode;
  2741. }
  2742. /**
  2743. * Validates a record.
  2744. *
  2745. * Validates unique fields, required fields, dataformat etc.
  2746. *
  2747. * @internal This method instantiates the entity's validator object, and
  2748. * delegates validation to that object.
  2749. *
  2750. * @param array $record The record to validate
  2751. * @param String $mode The mode for which validation is performed ('add' or 'update')
  2752. * @param array $ignoreList The list of attributes that should not be
  2753. * validated
  2754. */
  2755. function validate(&$record, $mode, $ignoreList = array())
  2756. {
  2757. $validateObj = &Adapto_ClassLoader::create($this->m_validate_class);
  2758. $validateObj->setEntity($this);
  2759. $validateObj->setRecord($record);
  2760. $validateObj->setIgnoreList($ignoreList);
  2761. $validateObj->setMode($mode);
  2762. return $validateObj->validate();
  2763. }
  2764. /**
  2765. * Add a unique field set.
  2766. *
  2767. * When you add a set of attributes using this method, any combination of
  2768. * values for the attributes should be unique. For example, if you pass
  2769. * array("name", "parent_id"), name does not have to be unique, parent_id
  2770. * does not have to be unique, but the combination should be unique.
  2771. *
  2772. * @param array $fieldArr The list of names of attributes that should be
  2773. * unique in combination.
  2774. */
  2775. function addUniqueFieldset($fieldArr)
  2776. {
  2777. sort($fieldArr);
  2778. if (!in_array($fieldArr, $this->m_uniqueFieldSets))
  2779. $this->m_uniqueFieldSets[] = $fieldArr;
  2780. }
  2781. /**
  2782. * Called by updateDb to load the original record inside the record if the
  2783. * EF_TRACK_CHANGES flag is set.
  2784. *
  2785. * NOTE: this method is made public because it's called from the update handler
  2786. *
  2787. * @param array $record
  2788. * @param array $excludes
  2789. * @param array $includes
  2790. */
  2791. public function trackChangesIfNeeded(&$record, $excludes = '', $includes = '')
  2792. {
  2793. if (!$this->hasFlag(EF_TRACK_CHANGES) || isset($record['atkorgrec']))
  2794. return;
  2795. // We need to add the NO_FILTER flag in case the new values would filter the record.
  2796. $flags = $this->m_flags;
  2797. $this->addFlag(EF_NO_FILTER);
  2798. $record["atkorgrec"] = $this->select()->where($record['atkprimkey'])->excludes($excludes)->includes($includes)->mode('edit')->firstRow();
  2799. // Need to restore the NO_FILTER bit back to its original value.
  2800. $this->m_flags = $flags;
  2801. }
  2802. /**
  2803. * Update a record in the database.
  2804. *
  2805. * The record should already exist in the database, or this method will
  2806. * fail.
  2807. *
  2808. * NOTE: Does not commit your transaction! If you are using a database that uses
  2809. * transactions you will need to call 'atkGetDb()->commit()' manually.
  2810. *
  2811. * @param array $record The record to update in the database.
  2812. * @param bool $exectrigger wether to execute the pre/post update triggers
  2813. * @param array $excludes exclude list (these attribute will *not* be updated)
  2814. * @param array $includes include list (only these attributes will be updated)
  2815. * @return boolean True if succesful, false if not.
  2816. */
  2817. function updateDb(&$record, $exectrigger = true, $excludes = "", $includes = "")
  2818. {
  2819. $db = &$this->getDb();
  2820. $query = &$db->createQuery();
  2821. $query->addTable($this->m_table);
  2822. // The record that must be updated is indicated by 'atkprimkey'
  2823. // (not by atkselector, since the primary key might have
  2824. // changed, so we use the atkprimkey, which is the value before
  2825. // any update happened.)
  2826. if ($record['atkprimkey'] != "") {
  2827. $this->trackChangesIfNeeded($record, $excludes, $includes);
  2828. if ($exectrigger)
  2829. $this->executeTrigger("preUpdate", $record);
  2830. $pk = $record['atkprimkey'];
  2831. $query->addCondition($pk);
  2832. $storelist = array("pre" => array(), "post" => array(), "query" => array());
  2833. foreach (array_keys($this->m_attribList) as $attribname) {
  2834. if ((!is_array($excludes) || !in_array($attribname, $excludes)) && (!is_array($includes) || in_array($attribname, $includes))) {
  2835. $p_attrib = &$this->m_attribList[$attribname];
  2836. if ($p_attrib->needsUpdate($record) || Adapto_in_array($attribname, $includes)) {
  2837. $storemode = $p_attrib->storageType("update");
  2838. if (hasFlag($storemode, PRESTORE))
  2839. $storelist["pre"][] = $attribname;
  2840. if (hasFlag($storemode, POSTSTORE))
  2841. $storelist["post"][] = $attribname;
  2842. if (hasFlag($storemode, ADDTOQUERY))
  2843. $storelist["query"][] = $attribname;
  2844. }
  2845. }
  2846. }
  2847. if (!$this->_storeAttributes($storelist["pre"], $record, "update"))
  2848. return false;
  2849. for ($i = 0, $_i = count($storelist["query"]); $i < $_i; $i++) {
  2850. $p_attrib = &$this->m_attribList[$storelist["query"][$i]];
  2851. $p_attrib->addToQuery($query, $this->m_table, "", $record, 1, "update"); // start at level 1
  2852. }
  2853. if (count($query->m_fields) && !$query->executeUpdate())
  2854. return false;
  2855. if ($this->hasFlag(EF_ML) && $record["atkmlsplit"] == "") {
  2856. $record["atkmlsplit"] = 1;
  2857. $mltool = Adapto_ClassLoader::getInstance("atk.utils.atkmlsplitter");
  2858. $mltool->updateMlRecords($this, $record, "update", $excludes, $includes);
  2859. }
  2860. if (!$this->_storeAttributes($storelist["post"], $record, "update"))
  2861. return false;
  2862. // Now we call a postUpdate function, that can be used to do some processing after the record
  2863. // has been saved.
  2864. if ($exectrigger)
  2865. return $this->executeTrigger("postUpdate", $record);
  2866. else
  2867. return true;
  2868. } else {
  2869. Adapto_Util_Debugger::debug("NOT UPDATING! NO SELECTOR SET!");
  2870. return false;
  2871. }
  2872. return true;
  2873. }
  2874. /**
  2875. * Call the store() method on a list of attributes.
  2876. * @access private
  2877. * @param array $storelist The list of attributes for which the
  2878. * store() method should be called.
  2879. * @param array $record The master record being stored.
  2880. * @param String $mode The storage mode ("add", "copy" or "update")
  2881. * @return boolean True if succesful, false if not.
  2882. */
  2883. function _storeAttributes($storelist, &$record, $mode)
  2884. {
  2885. // store special storage attributes.
  2886. for ($i = 0, $_i = count($storelist); $i < $_i; $i++) {
  2887. $p_attrib = &$this->m_attribList[$storelist[$i]];
  2888. if (!$p_attrib->store($this->getDb(), $record, $mode)) {
  2889. // something went wrong.
  2890. Adapto_Util_Debugger::debug("Store aborted. Attribute '" . $storelist[$i] . "' reported an error.");
  2891. return false;
  2892. }
  2893. }
  2894. return true;
  2895. }
  2896. /**
  2897. * Copy a record in the database.
  2898. *
  2899. * Primarykeys are automatically regenerated for the copied record. Any
  2900. * detail records (onetomanyrelation) are copied too. Refered records
  2901. * manytoonerelation) are not copied.
  2902. *
  2903. * @param array $record The record to copy.
  2904. * @param string $mode The mode we're in (mostly "copy")
  2905. * @return boolean True if succesful, false if not.
  2906. */
  2907. function copyDb(&$record, $mode = "copy")
  2908. {
  2909. // add original record
  2910. $original = $record; // force copy
  2911. $record['atkorgrec'] = $original;
  2912. //notify precopy listeners
  2913. $this->preNotify("precopy", $record);
  2914. // remove primarykey (copied record will get a new primary key)
  2915. unset($record["atkprimkey"]);
  2916. // remove trigger has been executed references
  2917. foreach (array_keys($record) as $key) {
  2918. if (preg_match('/^__executed.*$/', $key))
  2919. unset($record[$key]);
  2920. }
  2921. $this->preCopy($record);
  2922. return $this->addDb($record, true, $mode);
  2923. }
  2924. /**
  2925. * Get the current searchmode.
  2926. *
  2927. * @return mixed If there is one searchmode set for all attributes, this
  2928. * method returns a string. If there are searchmodes per
  2929. * attribute, an array of strings is returned.
  2930. */
  2931. function getSearchMode()
  2932. {
  2933. //The searchmode of an index should be used only once, therefore it uses
  2934. // atksinglesearchmode instead of atksearchmode.
  2935. if (isset($this->m_postvars["atksinglesearchmode"])) {
  2936. return $this->m_postvars["atksinglesearchmode"];
  2937. } else if (isset($this->m_postvars["atksearchmode"])) {
  2938. return $this->m_postvars["atksearchmode"];
  2939. }
  2940. return Adapto_Config::getGlobal("search_defaultmode");
  2941. }
  2942. /**
  2943. * Set some default for the selector.
  2944. *
  2945. * @param atkSelector $selector selector
  2946. * @param string $condition condition
  2947. * @param array $params condition bind parameters
  2948. */
  2949. protected function _initSelector(atkSelector $selector, $condition = null, $params = array())
  2950. {
  2951. $selector->orderBy($this->getOrder());
  2952. $selector->ignoreDefaultFilters($this->hasFlag(EF_NO_FILTER));
  2953. $selector->ignorePostvars(atkReadOptimizer());
  2954. if ($condition != null) {
  2955. $selector->where($condition, $params);
  2956. }
  2957. }
  2958. /**
  2959. * Retrieve records from the database using a handy helper class.
  2960. *
  2961. * @param string $condition condition
  2962. * @param array $params condition bind parameters
  2963. *
  2964. * @return atkSelector
  2965. */
  2966. public function select($condition = null, array $params = array())
  2967. {
  2968. $class = 'atkselector';
  2969. if (method_exists($this, 'selectDb') || method_exists($this, 'countDb')) {
  2970. $class = 'atkcompatselector';
  2971. } else if ($this->hasFlag(EF_ML)) {
  2972. $class = 'atkmlselector';
  2973. }
  2974. $selector = Adapto_ClassLoader::create('atk.utils.' . $class, $this);
  2975. $this->_initSelector($selector, $condition, $params);
  2976. return $selector;
  2977. }
  2978. /**
  2979. * Returns a record (array) as identified by a primary key (usually an "id" column),
  2980. * including applicable relations.
  2981. *
  2982. * @param int $pk primary key identifying the record
  2983. * @return array the associated record, or null if no such record exists
  2984. */
  2985. public function fetchByPk($pk)
  2986. {
  2987. return $this->select($this->getTable() . "." . $this->primaryKeyField() . '= ?', array($pk))->firstRow();
  2988. }
  2989. /**
  2990. * Add this entity to an existing query.
  2991. *
  2992. * Framework method, it should not be necessary to call this method
  2993. * directly.
  2994. * This method is used when adding the entire entity to an existing
  2995. * query, as part of a join.
  2996. * @todo The allfields parameter is too inflexible.
  2997. * @param atkQuery $query The query statement
  2998. * @param String $alias The aliasprefix to use for fields from this entity
  2999. * @param int $level The recursion level.
  3000. * @param boolean $allfields If set to true, all fields from the entity are
  3001. * added to the query. If set to false, only
  3002. * the primary key and fields from the desriptor
  3003. * are added.
  3004. * @param string $mode The mode we're in
  3005. * @param array $includes List of fields that should be included
  3006. */
  3007. function addToQuery(&$query, $alias = "", $level = 0, $allfields = false, $mode = "select", $includes = array())
  3008. {
  3009. if ($level >= 4)
  3010. return;
  3011. $usefieldalias = false;
  3012. if ($alias == "") {
  3013. $alias = $this->m_table;
  3014. } else {
  3015. $usefieldalias = true;
  3016. }
  3017. // If allfields is set, we load the entire record.. otherwise, we only
  3018. // load the important fields (descriptor and primary key fields)
  3019. // this is mainly used by onetoonerelation.
  3020. if ($allfields) {
  3021. $usedFields = array_keys($this->m_attribList);
  3022. } else {
  3023. $usedFields = Adapto_array_merge($this->descriptorFields(), $this->m_primaryKey, $includes);
  3024. foreach (array_keys($this->m_attribList) as $name)
  3025. if (is_object($this->m_attribList[$name]) && $this->m_attribList[$name]->hasFlag(AF_FORCE_LOAD))
  3026. $usedFields[] = $name;
  3027. $usedFields = array_unique($usedFields);
  3028. }
  3029. foreach ($usedFields as $usedfield) {
  3030. list($attribname) = explode(".", $usedfield);
  3031. $p_attrib = &$this->m_attribList[$attribname];
  3032. if (is_object($p_attrib)) {
  3033. $loadmode = $p_attrib->loadType("");
  3034. if ($loadmode && hasFlag($loadmode, ADDTOQUERY)) {
  3035. if ($usefieldalias)
  3036. $fieldaliasprefix = $alias . "_AE_";
  3037. $dummy = array();
  3038. $p_attrib->addToQuery($query, $alias, $fieldaliasprefix, $dummy, $level, $mode);
  3039. }
  3040. } else
  3041. Adapto_Util_Debugger::debug("$attribname is not an object?! Check your descriptor_def for non-existant fields");
  3042. }
  3043. if ($this->hasFlag(EF_ML)) {
  3044. $mltool = Adapto_ClassLoader::getInstance("atk.utils.atkmlsplitter");
  3045. $mltool->addMlCondition($query, $this, $mode, $alias);
  3046. }
  3047. }
  3048. /**
  3049. * Get search condition for this entity.
  3050. *
  3051. * @param atkQuery $query
  3052. * @param string $table
  3053. * @param string $alias
  3054. * @param string $value
  3055. * @param string $searchmode
  3056. * @return string The search condition
  3057. */
  3058. function getSearchCondition(&$query, $table, $alias, $value, $searchmode)
  3059. {
  3060. $usefieldalias = false;
  3061. if ($alias == "") {
  3062. $alias = $this->m_table;
  3063. } else {
  3064. $usefieldalias = true;
  3065. }
  3066. $searchConditions = array();
  3067. $attribs = $this->descriptorFields();
  3068. array_unique($attribs);
  3069. foreach ($attribs as $field) {
  3070. $p_attrib = &$this->getAttribute($field);
  3071. if (!is_object($p_attrib))
  3072. continue;
  3073. if ($usefieldalias)
  3074. $fieldaliasprefix = $alias . "_AE_";
  3075. // check if the entity has a searchcondition method defined for this attr
  3076. $methodName = $field . '_searchcondition';
  3077. if (method_exists($this, $methodName)) {
  3078. $searchCondition = $this->$methodName($query, $table, $value, $searchmode);
  3079. if ($searchCondition != "") {
  3080. $searchConditions[] = $searchCondition;
  3081. }
  3082. } else {
  3083. // checking for the getSearchCondition for backwards compatibility
  3084. if (method_exists($p_attrib, "getSearchCondition")) {
  3085. $attribsearchmode = $searchmode;
  3086. if (is_array($searchmode)) {
  3087. $attribsearchmode = $attribsearchmode[$p_attrib->m_name];
  3088. }
  3089. Adapto_Util_Debugger::debug("getSearchCondition: $table - $fieldaliasprefix");
  3090. $searchCondition = $p_attrib->getSearchCondition($query, $table, $value, $searchmode, $fieldaliasprefix);
  3091. if ($searchCondition != "")
  3092. $searchConditions[] = $searchCondition;
  3093. } else {
  3094. // if the attrib can't return it's searchcondition, we'll just add it to the query
  3095. // and hope for the best
  3096. $p_attrib->searchCondition($query, $table, $value, $searchmode, $fieldaliasprefix);
  3097. }
  3098. }
  3099. }
  3100. if (count($searchConditions)) {
  3101. return "(" . implode(" OR ", $searchConditions) . ")";
  3102. } else {
  3103. return "";
  3104. }
  3105. }
  3106. /**
  3107. * Save a new record to the database.
  3108. *
  3109. * The record is passed by reference, because any autoincrement field gets
  3110. * its value when stored to the database. The record is updated, so after
  3111. * the call to addDb you can use access the primary key fields.
  3112. *
  3113. * NOTE: Does not commit your transaction! If you are using a database that uses
  3114. * transactions you will need to call 'atkGetDb()->commit()' manually.
  3115. *
  3116. * @param array $record The record to save.
  3117. * @param boolean $exectrigger Indicates whether the postAdd trigger
  3118. * should be fired.
  3119. * @param string $mode The mode we're in
  3120. * @param array $excludelist List of attributenames that should be ignored
  3121. * and not stored in the database.
  3122. * @return boolean True if succesful, false if not.
  3123. */
  3124. function addDb(&$record, $exectrigger = true, $mode = "add", $excludelist = array())
  3125. {
  3126. if ($exectrigger)
  3127. if (!$this->executeTrigger("preAdd", $record, $mode))
  3128. return throw new Adapto_Exception("preAdd() failed!");
  3129. $db = &$this->getDb();
  3130. $query = &$db->createQuery();
  3131. $storelist = array("pre" => array(), "post" => array(), "query" => array());
  3132. $query->addTable($this->m_table);
  3133. foreach (array_keys($this->m_attribList) as $attribname) {
  3134. $p_attrib = &$this->m_attribList[$attribname];
  3135. if (!Adapto_in_array($attribname, $excludelist) && ($mode != "add" || $p_attrib->needsInsert($record))) {
  3136. $storemode = $p_attrib->storageType($mode);
  3137. if (hasFlag($storemode, PRESTORE))
  3138. $storelist["pre"][] = $attribname;
  3139. if (hasFlag($storemode, POSTSTORE))
  3140. $storelist["post"][] = $attribname;
  3141. if (hasFlag($storemode, ADDTOQUERY))
  3142. $storelist["query"][] = $attribname;
  3143. }
  3144. }
  3145. if (!$this->_storeAttributes($storelist["pre"], $record, $mode))
  3146. return false;
  3147. for ($i = 0, $_i = count($storelist["query"]); $i < $_i; $i++) {
  3148. $p_attrib = &$this->m_attribList[$storelist["query"][$i]];
  3149. $p_attrib->addToQuery($query, $this->m_table, "", $record, 1, 'add'); // start at level 1
  3150. }
  3151. if (!$query->executeInsert()) {
  3152. Adapto_Util_Debugger::debug("executeInsert failed..");
  3153. return false;
  3154. }
  3155. // new primary key
  3156. $record["atkprimkey"] = $this->primaryKey($record);
  3157. if ($this->hasFlag(EF_ML) && $record["atkmlsplit"] == "") {
  3158. $record["atkmlsplit"] = 1;
  3159. $mltool = Adapto_ClassLoader::getInstance("atk.utils.atkmlsplitter");
  3160. $mltool->updateMlRecords($this, $record, $mode);
  3161. }
  3162. if (!$this->_storeAttributes($storelist["post"], $record, $mode)) {
  3163. Adapto_Util_Debugger::debug("_storeAttributes failed..");
  3164. return false;
  3165. }
  3166. // Now we call a postAdd function, that can be used to do some processing after the record
  3167. // has been saved.
  3168. if ($exectrigger && !$this->executeTrigger("postAdd", $record, $mode))
  3169. return false;
  3170. return true;
  3171. }
  3172. /**
  3173. * Executes a trigger on a add,update or delete action
  3174. *
  3175. * To prevent triggers from executing twice, the method stores an
  3176. * indication in the record when a trigger is executed.
  3177. * ('__executed<triggername>')
  3178. *
  3179. * @param string $trigger function, such as 'postUpdate'
  3180. * @param array $record record on which action is performed
  3181. * @param string $mode mode like add or update
  3182. * @return bool true on case of success or when the trigger isn't returning anything (assumes success)
  3183. */
  3184. function executeTrigger($trigger, &$record, $mode = null)
  3185. {
  3186. if (!isset($record["__executed" . $trigger])) {
  3187. $record["__executed" . $trigger] = true;
  3188. $return = $this->$trigger($record, $mode);
  3189. if ($return === NULL) {
  3190. Adapto_Util_Debugger::debug("Undefined return: " . $this->atkEntityType() . ".$trigger doesn't return anything, it should return a boolean!", DEBUG_WARNING);
  3191. $return = true;
  3192. }
  3193. if (!$return) {
  3194. Adapto_Util_Debugger::debug($this->atkEntityType() . ".$trigger failed!");
  3195. return false;
  3196. }
  3197. for ($i = 0, $_i = count($this->m_triggerListeners); $i < $_i; $i++) {
  3198. $listener = &$this->m_triggerListeners[$i];
  3199. $return = $listener->notify($trigger, $record, $mode);
  3200. if ($return === NULL) {
  3201. Adapto_Util_Debugger::debug(
  3202. "Undefined return: " . $this->atkEntityType() . ", " . get_class($listener)
  3203. . ".notify('$trigger', ...) doesn't return anything, it should return a boolean!", DEBUG_WARNING);
  3204. $return = true;
  3205. }
  3206. if (!$return) {
  3207. Adapto_Util_Debugger::debug($this->atkEntityType() . ", " . get_class($listener) . ".notify('$trigger', ...) failed!");
  3208. return false;
  3209. }
  3210. }
  3211. }
  3212. return true;
  3213. }
  3214. /**
  3215. * Delete record(s) from the database.
  3216. *
  3217. * After deletion, the postDel() trigger in the entity method is called, and
  3218. * on any attribute that has the AF_CASCADE_DELETE flag set, the delete()
  3219. * method is invoked.
  3220. *
  3221. * NOTE: Does not commit your transaction! If you are using a database that uses
  3222. * transactions you will need to call 'atkGetDb()->commit()' manually.
  3223. *
  3224. * @todo There's a discrepancy between updateDb, addDb and deleteDb:
  3225. * There should be a deleteDb which accepts a record, instead
  3226. * of a selector.
  3227. * @param String $selector SQL expression used as where-clause that
  3228. * indicates which records to delete.
  3229. * @param bool $exectrigger wether to execute the pre/post triggers
  3230. * @param bool $failwhenempty determine whether to throw an error if there is nothing to delete
  3231. * @returns boolean True if successful, false if not.
  3232. */
  3233. function deleteDb($selector, $exectrigger = true, $failwhenempty = false)
  3234. {
  3235. $recordset = $this->selectDb($selector, "", "", "", "", "delete");
  3236. // nothing to delete, throw an error (determined by $failwhenempty)!
  3237. if (count($recordset) == 0) {
  3238. atkwarning($this->atkentitytype() . "->deleteDb($selector): 0 records found, not deleting anything.");
  3239. return !$failwhenempty;
  3240. }
  3241. if ($exectrigger) {
  3242. for ($i = 0, $_i = count($recordset); $i < $_i; $i++) {
  3243. $return = $this->executeTrigger("preDelete", $recordset[$i]);
  3244. if (!$return)
  3245. return false;
  3246. }
  3247. }
  3248. if (count($this->m_cascadingAttribs) > 0) {
  3249. for ($i = 0, $_i = count($recordset); $i < $_i; $i++) {
  3250. for ($j = 0, $_j = count($this->m_cascadingAttribs); $j < $_j; $j++) {
  3251. $p_attrib = $this->m_attribList[$this->m_cascadingAttribs[$j]];
  3252. if (isset($recordset[$i][$this->m_cascadingAttribs[$j]]) && !$p_attrib->isEmpty($recordset[$i])) {
  3253. if (!$p_attrib->delete($recordset[$i])) {
  3254. // error
  3255. return false;
  3256. }
  3257. }
  3258. }
  3259. }
  3260. }
  3261. $query = $this->getDb()->createQuery();
  3262. $query->addTable($this->m_table);
  3263. $query->addCondition($selector);
  3264. if ($query->executeDelete()) {
  3265. if ($exectrigger) {
  3266. for ($i = 0, $_i = count($recordset); $i < $_i; $i++) {
  3267. $return = ($this->executeTrigger("postDel", $recordset[$i]) && $this->executeTrigger("postDelete", $recordset[$i]));
  3268. if (!$return)
  3269. return false;
  3270. }
  3271. }
  3272. return true;
  3273. } else {
  3274. return false;
  3275. }
  3276. }
  3277. /**
  3278. * Function that is called by the framework, right after a new record has
  3279. * been saved to the database.
  3280. *
  3281. * This function does essentially nothing, but it can be overriden in
  3282. * derived classes if you want to do something special after you saved a
  3283. * record.
  3284. *
  3285. * @param array $record The record that has just been saved.
  3286. * @param String $mode The 'mode' indicates whether the added record was a
  3287. * completely new record ("add") or a copy ("copy").
  3288. * @return boolean True if succesful, false if not.
  3289. */
  3290. function postAdd($record, $mode = "add")
  3291. {
  3292. // Do nothing
  3293. return true;
  3294. }
  3295. /**
  3296. * Function that is called by the framework, just before a new record will
  3297. * be saved to the database.
  3298. *
  3299. * This function does essentially nothing, but it can be overriden in
  3300. * derived classes if you want to modify the record just before it will
  3301. * be saved.
  3302. *
  3303. * @param array $record The record that will be saved to the database.
  3304. */
  3305. function preAdd(&$record)
  3306. {
  3307. // Do nothing
  3308. return true;
  3309. }
  3310. /**
  3311. * Function that is called by the framework, right after an existing
  3312. * record has been updated in the database.
  3313. *
  3314. * This function does essentially nothing, but it can be overriden in
  3315. * derived classes if you want to do something special after the record is
  3316. * updated.
  3317. *
  3318. * If the EF_TRACK_CHANGES flag is present for the entity, both the new
  3319. * and the original record are passed to this method. The original
  3320. * record is stored in the new record, in $record["atkorgrec"].
  3321. *
  3322. * @param array $record The record that has just been updated in the
  3323. * database.
  3324. * @return boolean True if succesful, false if not.
  3325. */
  3326. function postUpdate($record)
  3327. {
  3328. // Do nothing
  3329. return true;
  3330. }
  3331. /**
  3332. * Function that is called by the framework, just before an existing
  3333. * record will be saved to the database.
  3334. *
  3335. * This function does essentially nothing, but it can be overriden in
  3336. * derived classes if you want to modify the record just before it will
  3337. * be saved.
  3338. *
  3339. * @param array $record The record that will be updated in the database.
  3340. * @return bool Wether or not we succeeded in what we wanted to do.
  3341. */
  3342. function preUpdate(&$record)
  3343. {
  3344. // Do nothing
  3345. return true;
  3346. }
  3347. /**
  3348. * Function that is called by the framework, right before a record will be
  3349. * deleted. Should this method return false the deleting will halt.
  3350. *
  3351. * This function does essentially nothing, but it can be overriden in
  3352. * derived classes if you want to do something special after a record is
  3353. * deleted.
  3354. *
  3355. * If this function returns false the delete action will not continue.
  3356. *
  3357. * @param array $record The record that will be deleted.
  3358. */
  3359. function preDelete($record)
  3360. {
  3361. return true;
  3362. }
  3363. /**
  3364. * Function that is called by the framework, right after a record has been
  3365. * deleted.
  3366. *
  3367. * This function does essentially nothing, but it can be overriden in
  3368. * derived classes if you want to do something special after a record is
  3369. * deleted.
  3370. *
  3371. * @param array $record The record that has just been deleted.
  3372. * @return bool Wether or not we succeeded in what we wanted to do.
  3373. */
  3374. function postDelete($record)
  3375. {
  3376. // Do nothing
  3377. return true;
  3378. }
  3379. /**
  3380. * Function that is called by the framework, right before a copied record
  3381. * is stored to the database.
  3382. *
  3383. * This function does nothing, but it can be overriden in derived classes
  3384. * if you want to do some processing on a record before it is
  3385. * being copied.
  3386. * Typical usage would be: Suppose you have a field named 'title' in a
  3387. * record. In the preCopy method, you could change the title field of the
  3388. * record to 'Copy of ..', so the user can distinguish between the
  3389. * original and the copy.
  3390. *
  3391. * @param array $record A reference to the copied record. You can change the
  3392. * contents of the record, since it is passed by
  3393. * reference.
  3394. */
  3395. function preCopy(&$record)
  3396. {
  3397. }
  3398. /**
  3399. * Function that is called for each record in a recordlist, to determine
  3400. * what actions may be performed on the record.
  3401. *
  3402. * This function does nothing, but it can be overriden in derived classes,
  3403. * to make custom actions for certain records.
  3404. * The array with actions (edit, delete, etc.) is passed to the function
  3405. * and can be modified.
  3406. * To create a new action, just do $actions["new_action"]=$url;
  3407. * in the derived function.
  3408. * To disable existing actions, for example the edit action, for a record,
  3409. * use: unset($actions["edit"]);
  3410. *
  3411. * @param array $record The record for which the actions need to be
  3412. * determined.
  3413. * @param array &$actions Reference to an array with the already defined
  3414. * actions. This is an associative array with the action
  3415. * identifier as key, and an url as value. Actions can be
  3416. * removed from it, or added to the array.
  3417. * @param array &$mraactions List of multirecordactions that are supported for
  3418. * the passed record.
  3419. */
  3420. function recordActions($record, &$actions, &$mraactions)
  3421. {
  3422. // Do nothing.
  3423. }
  3424. /**
  3425. * Registers a function/method that is called for each record in a recordlist,
  3426. * to determine what actions may be performed on the record.
  3427. *
  3428. * The callback receives the record, a reference to the record actions and
  3429. * a reference to the MRA actions as arguments.
  3430. *
  3431. */
  3432. public function registerRecordActionsCallback($callback)
  3433. {
  3434. if (is_callable($callback, false, $callableName)) {
  3435. if (is_array($callback)) {
  3436. if (!method_exists($callback[0], $callback[1])) {
  3437. throw new Adapto_Exception("The registered record actions callback method '$callableName' doesn't exist");
  3438. return;
  3439. }
  3440. }
  3441. $this->m_recordActionsCallbacks[] = $callback;
  3442. } else {
  3443. throw new Adapto_Exception("The registered record actions callback '$callableName' is not callable");
  3444. return;
  3445. }
  3446. }
  3447. /**
  3448. * Function that is called for each record in a recordlist, to determine
  3449. * what actions may be performed on the record.
  3450. *
  3451. * This function is a framework method and should not be called directly.
  3452. * It should not be overridden either.
  3453. *
  3454. * To change the record actions, either override Adapto_Entity::recordActions() in you entity,
  3455. * or call Adapto_Entity::registerRecordActionsCallback to register a callback.
  3456. *
  3457. * @param array $record The record for which the actions need to be
  3458. * determined.
  3459. * @param array &$actions Reference to an array with the already defined
  3460. * actions. This is an associative array with the action
  3461. * identifier as key, and an url as value. Actions can be
  3462. * removed from it, or added to the array.
  3463. * @param array &$mraactions List of multirecordactions that are supported for
  3464. * the passed record.
  3465. * @return void;
  3466. */
  3467. public function collectRecordActions($record, &$actions, &$mraactions)
  3468. {
  3469. $this->recordActions($record, $actions, $mraactions);
  3470. foreach ($this->m_recordActionsCallbacks as $callback) {
  3471. call_user_func_array($callback, array($record, &$actions, &$mraactions));
  3472. }
  3473. }
  3474. /**
  3475. * Retrieve the security key of an action.
  3476. *
  3477. * Returns the privilege required to perform a certain action.
  3478. * Usually, the privilege and the action are equal, but in m_securityMap,
  3479. * aliasses may be defined.
  3480. * @param String $action The action for which you want to determine the
  3481. * privilege.
  3482. * @return String The security privilege required to perform the action.
  3483. */
  3484. function securityKey($action)
  3485. {
  3486. if (!isset($this->m_securityMap[$action]))
  3487. return $action;
  3488. return $this->m_securityMap[$action];
  3489. }
  3490. /**
  3491. * Returns the type of this entity. (This is *not* the full ATK entity type;
  3492. * see atkEntityType() for the full entity type.)
  3493. *
  3494. * @return string type
  3495. */
  3496. public function getType()
  3497. {
  3498. return $this->m_type;
  3499. }
  3500. /**
  3501. * Returns the module for this entity.
  3502. *
  3503. * @return string entity
  3504. */
  3505. public function getModule()
  3506. {
  3507. return $this->m_module;
  3508. }
  3509. /**
  3510. * Returns the current action for this entity.
  3511. *
  3512. * @return string action
  3513. */
  3514. public function getAction()
  3515. {
  3516. return $this->m_action;
  3517. }
  3518. /**
  3519. * Get the full atkentitytype of this entity (module.entitytype notation). This is sometimes
  3520. * referred to as the entity name (or entityname) or entity string.
  3521. *
  3522. * @return String The atkentitytype of the entity.
  3523. */
  3524. function atkEntityType()
  3525. {
  3526. return (empty($this->m_module) ? "" : $this->m_module . ".") . $this->m_type;
  3527. }
  3528. /**
  3529. * This function determines if the user has the privilege to perform a certain
  3530. * action on the entity.
  3531. *
  3532. * @param String $action The action to be checked.
  3533. * @param array $record The record on which the action is to be performed.
  3534. * The standard implementation ignores this
  3535. * parameter, but derived classes may override this
  3536. * method to implement their own record based
  3537. * security policy. Keep in mind that a record is not
  3538. * passed in every occasion. The method is called
  3539. * several times without a record, to just see if
  3540. * the user has the privilege for the action
  3541. * regardless of the record being processed.
  3542. *
  3543. * @return boolean True if the action may be performed, false if not.
  3544. */
  3545. function allowed($action, $record = "")
  3546. {
  3547. $secMgr = &atkGetSecurityManager();
  3548. $alias = $this->atkEntityType();
  3549. $this->resolveEntityTypeAndAction($alias, $action);
  3550. return ($this->hasFlag(EF_NO_SECURITY) || in_array($action, $this->m_unsecuredActions) || $secMgr->allowed($alias, $action)
  3551. || (isset($this->m_securityImplied[$action]) && $secMgr->allowed($alias, $this->m_securityImplied[$action])));
  3552. }
  3553. /**
  3554. * Resolves a possible entity / action alias for the given entity / action.
  3555. * The given entity alias and action are updated depending on
  3556. * the found mapping.
  3557. *
  3558. * @param string $alias entity type
  3559. * @param string $action action name
  3560. */
  3561. function resolveEntityTypeAndAction(&$alias, &$action)
  3562. {
  3563. if (!empty($this->m_securityAlias)) {
  3564. $alias = $this->m_securityAlias;
  3565. }
  3566. // Resolve action
  3567. $action = $this->securityKey($action);
  3568. // If action contains a dot, it's a complete entityname.action or modulename.entityname.action alias.
  3569. // Else, it's only an action alias, and we use the default entity.
  3570. if (strpos($action, ".") !== false) {
  3571. $complete = explode(".", $action);
  3572. if (count($complete) == 3) {
  3573. $alias = $complete[0] . "." . $complete[1];
  3574. $action = $complete[2];
  3575. } else {
  3576. $alias = $this->m_module . "." . $complete[0];
  3577. $action = $complete[1];
  3578. }
  3579. }
  3580. }
  3581. /**
  3582. * Set the security alias of an entity.
  3583. *
  3584. * By default an entity has it's own set of privileges. With this method,
  3585. * the privileges of another entity can be used. This is useful when you
  3586. * have a master/detail relationship, and people may manipulate details
  3587. * when they have privileges on the master entity.
  3588. * Note: When setting an alias for the entity, the entity no longer has to
  3589. * have a registerEntity call in the getEntitys method in module.inc.
  3590. *
  3591. * @param String $alias The entity (module.entityname) to set as a security
  3592. * alias for this entity.
  3593. */
  3594. function setSecurityAlias($alias)
  3595. {
  3596. $this->m_securityAlias = $alias;
  3597. }
  3598. /**
  3599. * Returns the entity's security alias (if set).
  3600. *
  3601. * @return string security alias
  3602. */
  3603. function getSecurityAlias()
  3604. {
  3605. return $this->m_securityAlias;
  3606. }
  3607. /**
  3608. * Disable privilege checking for an action.
  3609. *
  3610. * This method disables privilege checks for the specified action, for the
  3611. * duration of the current http request.
  3612. * @param String $action The name of the action for which security is
  3613. * disabled.
  3614. */
  3615. function addAllowedAction($action)
  3616. {
  3617. if (is_array($action)) {
  3618. $this->m_unsecuredActions = Adapto_array_merge($this->m_unsecuredActions, $action);
  3619. } else {
  3620. $this->m_unsecuredActions[] = $action;
  3621. }
  3622. }
  3623. /**
  3624. * Retrieve help link for the current entity.
  3625. * @return String Complete html link, linking to the help popup.
  3626. */
  3627. function getHelp()
  3628. {
  3629. $res = array();
  3630. $res["helpurl"] = $this->helpUrl();
  3631. if ($res["helpurl"] != "") {
  3632. $page = &$this->getPage();
  3633. $page->register_script(Adapto_Config::getGlobal("atkroot") . "atk/javascript/newwindow.js");
  3634. $res["helplabel"] = atktext("help");
  3635. $res["helplink"] = '<a href="' . $res["helpurl"] . '">' . $res["helplabel"] . '</a>';
  3636. }
  3637. return $res;
  3638. }
  3639. /**
  3640. * Get img tag for lock icon.
  3641. * @param boolean $lockstatus True if the record is locked, false if not.
  3642. * @return String HTML image tag with the correct lock icon.
  3643. */
  3644. function getLockStatusIcon($lockstatus)
  3645. {
  3646. if ($lockstatus) {
  3647. $icon = atkTheme::getInstance()->iconPath('lock_' . $this->getLockMode(), 'lock', $this->m_module);
  3648. return '<img src="' . $icon . '" border="0" name="_lock_">';
  3649. }
  3650. return "";
  3651. }
  3652. /**
  3653. * Get the help url for this entity.
  3654. *
  3655. * Retrieves the url of the help popup, if there is help available for
  3656. * this entity.
  3657. * @return String The help url, or an empty string if help is not
  3658. * available.
  3659. */
  3660. function helpUrl()
  3661. {
  3662. $language = Adapto_Config::getGlobal("language");
  3663. $entity = $this->m_type;
  3664. $file = moduleDir($this->m_module) . "help/" . $language . "/help." . $entity . ".inc";
  3665. $helpmodule = "";
  3666. if (file_exists($file)) {
  3667. $helpmodule = $this->m_module;
  3668. } else {
  3669. // bwc
  3670. $file = "help/" . $language . "/help." . $entity . ".inc";
  3671. if (!file_exists($file)) {
  3672. // no help available..
  3673. return "";
  3674. }
  3675. }
  3676. $name = atktext("help");
  3677. return atkPopup('atk/popups/help.inc', 'entity=' . $entity . ($helpmodule != "" ? "&module=" . $helpmodule : ""), $name, 650, 650, 'yes', 'no');
  3678. }
  3679. /**
  3680. * Invoke the handler for an action.
  3681. *
  3682. * If there is a known registered external handler method for the
  3683. * specified action, this method will call it. If there is no custom
  3684. * external handler, the atkActionHandler object is determined and the
  3685. * actionis invoked on the actionhandler.
  3686. * @param String $action the entity action
  3687. */
  3688. function callHandler($action)
  3689. {
  3690. Adapto_Util_Debugger::debug("Adapto_Entity::callHandler(); action: " . $action);
  3691. $handler = &atkGetEntityHandler($this->m_type, $action);
  3692. // handler function
  3693. if ($handler != NULL && is_string($handler) && function_exists($handler)) {
  3694. Adapto_Util_Debugger::debug("Adapto_Entity::callHandler: Calling external handler function for '" . $action . "'");
  3695. $handler($this, $action);
  3696. }
  3697. // handler object
  3698. elseif ($handler != NULL && $handler instanceof atkActionHandler) {
  3699. Adapto_Util_Debugger::debug("Adapto_Entity::callHandler:Using override/existing atkActionHandler " . get_class($handler) . " class for '" . $action . "'");
  3700. $handler->handle($this, $action, $this->m_postvars);
  3701. }
  3702. // no (valid) handler
  3703. else {
  3704. Adapto_Util_Debugger::debug("Calling default handler function for '" . $action . "'");
  3705. $this->m_handler = &$this->getHandler($action);
  3706. $this->m_handler->handle($this, $action, $this->m_postvars);
  3707. }
  3708. }
  3709. /**
  3710. * Get the atkActionHandler object for a certain action.
  3711. *
  3712. * The default implementation returns a default handler for the action,
  3713. * but derived classes may override this to return a custom handler.
  3714. * @param String $action The action for which the handler is retrieved.
  3715. * @return atkActionHandler The action handler.
  3716. */
  3717. function &getHandler($action)
  3718. {
  3719. Adapto_Util_Debugger::debug("Adapto_Entity::getHandler(); action: " . $action);
  3720. // for backwards compatibility we first check if a handler exists without using the module name
  3721. $handler = &atkGetEntityHandler($this->m_type, $action);
  3722. // then check if a handler exists registered including the module name
  3723. if ($handler == NULL) {
  3724. $handler = &atkGetEntityHandler($this->atkEntityType(), $action);
  3725. }
  3726. // The entity handler might return a class, then we need to instantiate the handler
  3727. if (is_string($handler) && !function_exists($handler) && atkimport($handler)) {
  3728. $handler = &Adapto_ClassLoader::create($handler);
  3729. }
  3730. // The entity handler might return a function as entityhandler. We cannot
  3731. // return a function so we ignore this option.
  3732. // @todo why not implement a base atkfunctionactionhandler which just calls the given function?
  3733. // this would probably only work fine when using PHP5, but's better then nothing?
  3734. // or why support functions at all?!
  3735. // handler object
  3736. if ($handler != NULL && is_subclass_of($handler, "atkActionHandler")) {
  3737. Adapto_Util_Debugger::debug("Adapto_Entity::getHandler: Using existing atkActionHandler " . get_class($handler) . " class for '" . $action . "'");
  3738. $handler->setEntity($this);
  3739. $handler->setAction($action);
  3740. } else {
  3741. $handler = &atkActionHandler::getDefaultHandler($action);
  3742. $handler->setEntity($this);
  3743. $handler->setPostvars($this->m_postvars);
  3744. $handler->setAction($action);
  3745. //If we use a default handler we need to register it to this entity
  3746. //because we might call it a second time.
  3747. Adapto_Util_Debugger::debug("Adapto_Entity::getHandler: Register default atkActionHandler for " . $this->m_type . " action: '" . $action . "'");
  3748. atkRegisterEntityHandler($this->m_type, $action, $handler);
  3749. }
  3750. return $handler;
  3751. }
  3752. /**
  3753. * Sets the search action.
  3754. *
  3755. * The search action is the action that will be performed
  3756. * if only a single record is found after doing a certain search query.
  3757. *
  3758. * You can specify more then 1 action. If the user isn't allowed to
  3759. * execute the 1st action, the 2nd action will be used, etc. If you
  3760. * want to pass multiple actions, just pass multiple params (function
  3761. * has a variable number of arguments).
  3762. * @todo Using func_get_args is non-standard. It's cleaner to accept an
  3763. * array.
  3764. * @param String $action The name of the action.
  3765. */
  3766. function setSearchAction()
  3767. {
  3768. $this->m_search_action = func_get_args();
  3769. }
  3770. /**
  3771. * This function resorts the attribIndexList and attribList.
  3772. *
  3773. * This is necessary if you add attributes *after* init() is already
  3774. * called, and you set an order for those attributes.
  3775. */
  3776. function attribSort()
  3777. {
  3778. usort($this->m_attribIndexList, array("atkentity", "attrib_cmp"));
  3779. // after sorting we need to update the attribute indices
  3780. $attrs = array();
  3781. foreach ($this->m_attribIndexList as $index => $info) {
  3782. $attr = $this->getAttribute($info['name']);
  3783. $attr->m_index = $index;
  3784. $attrs[$info['name']] = $attr;
  3785. }
  3786. $this->m_attribList = $attrs;
  3787. }
  3788. /**
  3789. * Search all records for the occurance of a certain expression.
  3790. *
  3791. * This function searches in all fields that are not AF_HIDE_SEARCH, for
  3792. * a certain expression (substring match). The search performed is an
  3793. * 'or' search. If any of the fields contains the expression, the record
  3794. * is added to the resultset.\
  3795. *
  3796. * Currently, searchDb only searches those attributes that are of type
  3797. * string or text.
  3798. *
  3799. * @param String $expression The keyword to search for.
  3800. * @param string $searchmethod
  3801. * @return array Set of records matching the keyword.
  3802. */
  3803. function searchDb($expression, $searchmethod = "OR")
  3804. {
  3805. // Set default searchmethod to OR (put it in m_postvars, because selectDb
  3806. // will use m_postvars to built it's search conditions).
  3807. $this->m_postvars['atksearchmethod'] = $searchmethod;
  3808. // To perform the search, we fill atksearch, so selectDb automatically
  3809. // searches. Because an atksearch variable may have already been set,
  3810. // we save it to restore it after the query.
  3811. $orgsearch = atkArrayNvl($this->m_postvars, "atksearch");
  3812. // Built whereclause.
  3813. foreach (array_keys($this->m_attribList) as $attribname) {
  3814. $p_attrib = &$this->m_attribList[$attribname];
  3815. // Only search in fields that aren't explicitly hidden from search
  3816. if (!$p_attrib->hasFlag(AF_HIDE_SEARCH) && (in_array($p_attrib->dbFieldType(), array("string", "text")) || $p_attrib->hasFlag(AF_SEARCHABLE))) {
  3817. $this->m_postvars['atksearch'][$attribname] = $expression;
  3818. }
  3819. }
  3820. // We load records in admin mode, se we are certain that all fields are added.
  3821. $recs = $this->selectDb("", "", "", $this->m_listExcludes, "", "admin");
  3822. // Restore original atksearch
  3823. $this->m_postvars['atksearch'] = $orgsearch;
  3824. return $recs;
  3825. }
  3826. /**
  3827. * Determine the url for the feedbackpage.
  3828. *
  3829. * Output is dependent on the feedback configuration. If feedback is not
  3830. * enabled for the action, this method returns an empty string, so the
  3831. * result of this method can be passed directly to the redirect() method
  3832. * after completing the action.
  3833. *
  3834. * The $record parameter is ignored by the default implementation, but
  3835. * derived classes may override this method to perform record-specific
  3836. * feedback.
  3837. * @param String $action The action that was performed
  3838. * @param int $status The status of the action.
  3839. * @param array $record The record on which the action was performed.
  3840. * @param String $message An optional message to pass to the feedbackpage,
  3841. * for example to explain the reason why an action
  3842. * failed.
  3843. * @param int $levelskip Number of levels to skip
  3844. * @return String The feedback url.
  3845. */
  3846. function feedbackUrl($action, $status, $record = "", $message = "", $levelskip = null)
  3847. {
  3848. $controller = &atkcontroller::getInstance();
  3849. $controller->setEntity($this);
  3850. return $controller->feedbackUrl($action, $status, $record, $message, $levelskip);
  3851. }
  3852. /**
  3853. * Validates if a filter is valid for this entity.
  3854. *
  3855. * A filter is considered valid if it doesn't contain any fields that are
  3856. * not part of the entity.
  3857. *
  3858. * Why isn't this used more often???
  3859. *
  3860. * @param String $filter The filter expression to validate
  3861. * @returns String Returns $filter if the filter is valid or a empty
  3862. * string if not.
  3863. */
  3864. public function validateFilter($filter)
  3865. {
  3866. // If the filter is blank
  3867. // Or we can't find the target field
  3868. if ($filter === '') {
  3869. return $filter;
  3870. }
  3871. $targetField = $this->getFirstTargetFieldFromFilterSql($filter);
  3872. if (!$targetField) {
  3873. atkwarning($this->atkEntityType() . '->' . __FUNCTION__ . "($filter): Disallowed because it has no target field");
  3874. // Don't allow the filter
  3875. return '';
  3876. }
  3877. // Separate the table name from the column name
  3878. $targetDetails = explode('.', $targetField);
  3879. $targetTable = $this->m_table;
  3880. $targetColumn = array_pop($targetDetails);
  3881. // If no table is specified then it is implied that it is the current table
  3882. if (count($targetDetails) == 1) {
  3883. $targetTable = array_pop($targetDetails);
  3884. }
  3885. // If the table isn't $this one
  3886. if (strtolower(trim($targetTable)) !== strtolower($this->m_table) && !($this->getAttribute($targetTable) instanceof atkManyToOneRelation)) {
  3887. atkwarning(
  3888. $this->atkEntityType() . '->' . __FUNCTION__ . "($filter): Disallowed because " . strtolower(trim($targetTable)) . " !== "
  3889. . strtolower($this->m_table) . ' and not a valid many-to-one relation.');
  3890. return '';
  3891. }
  3892. // Or the column doesn't belong to $this
  3893. if (!($this->getAttribute($targetTable) instanceof atkManyToOneRelation) && !in_array($targetColumn, array_keys($this->m_attribList))) {
  3894. atkwarning($this->atkEntityType() . '->' . __FUNCTION__ . "($filter): Disallowed because target column $targetColumn isn't in entity");
  3895. return "";
  3896. }
  3897. return $filter;
  3898. }
  3899. /**
  3900. * Get the targeted field and table from a snippet of filter string sql
  3901. *
  3902. * @param string $sql is the filter string sql
  3903. * @return string the target table and field or an empty string
  3904. */
  3905. function getFirstTargetFieldFromFilterSql($sql)
  3906. {
  3907. // All standard SQL operators
  3908. $sqloperators = array('=', '<>', '>', '<', '>=', '<=', 'BETWEEN', 'LIKE', 'IN', 'IS', 'NOT IN', '&');
  3909. $sqlOperatorsString = implode('|', array_map('preg_quote', $sqloperators));
  3910. $matches = array();
  3911. preg_match("/^(\w.+?)\s*({$sqlOperatorsString})/", str_replace('`', '', trim($sql)), $matches);
  3912. if (count($matches) != 3)
  3913. return '';
  3914. return $matches[1];
  3915. }
  3916. /**
  3917. * Add a stylesheet to the page.
  3918. *
  3919. * The theme engine is used to determine the path, and load the correct
  3920. * stylesheet.
  3921. * @param String $style The filename of the stylesheet (without path).
  3922. */
  3923. function addStyle($style)
  3924. {
  3925. $theme = Adapto_ClassLoader::getInstance("Adapto_Ui_Theme");
  3926. $page = &$this->getPage();
  3927. $page->register_style($theme->stylePath($style));
  3928. }
  3929. /**
  3930. * Sets numbering of the attributes to begin with the number that was passed to it,
  3931. * or defaults to 1.
  3932. * @param mixed $number the number that the first attribute begins with
  3933. */
  3934. function setNumbering($number = 1)
  3935. {
  3936. $this->m_numbering = $number;
  3937. }
  3938. /**
  3939. * Gets the numbering of the attributes
  3940. * @return mixed the number whith which the numbering starts
  3941. */
  3942. function getNumbering()
  3943. {
  3944. return $this->m_numbering;
  3945. }
  3946. /**
  3947. * Set the security of one or more actions action the same as other actions.
  3948. * If $mapped is empty $action has to be an array. The key would be used as action and would be mapped to the value.
  3949. * If $mapped is not empty $action kan be a string containing one action of an array with one or more action. In both
  3950. * cases al actions would be mapped to $mappped
  3951. * @param Mixed $action The action that has to be mapped
  3952. * @param String $mapped The action on witch $action has to be mapped
  3953. */
  3954. function addSecurityMap($action, $mapped = "")
  3955. {
  3956. if ($mapped != "")
  3957. if (!is_array($action)) {
  3958. $this->m_securityMap[$action] = $mapped;
  3959. $this->changeMapping($action, $mapped);
  3960. } else {
  3961. foreach ($action as $value) {
  3962. $this->m_securityMap[$value] = $mapped;
  3963. $this->changeMapping($value, $mapped);
  3964. }
  3965. }
  3966. else if (is_array($action))
  3967. foreach ($action as $key => $value) {
  3968. $this->m_securityMap[$key] = $value;
  3969. $this->changeMapping($key, $value);
  3970. }
  3971. }
  3972. /**
  3973. * change the securitymap that already exist. Where actions are mapped on $oldmapped change it by $newmapped
  3974. * @param string $oldmapped the old value
  3975. * @param string $newmapped the new value with replace the old one
  3976. */
  3977. function changeMapping($oldmapped, $newmapped)
  3978. {
  3979. foreach ($this->m_securityMap as $key => $value) {
  3980. if ($value == $oldmapped)
  3981. $this->m_securityMap[$key] = $newmapped;
  3982. }
  3983. }
  3984. /**
  3985. * Add an atkActionListener to the entity.
  3986. *
  3987. * @param atkActionListener $listener
  3988. */
  3989. function addListener(&$listener)
  3990. {
  3991. $listener->setEntity($this);
  3992. if (is_a($listener, 'atkActionListener')) {
  3993. $this->m_actionListeners[] = &$listener;
  3994. } else if (is_a($listener, 'atkTriggerListener')) {
  3995. $this->m_triggerListeners[] = &$listener;
  3996. } else {
  3997. Adapto_Util_Debugger::debug('Adapto_Entity::addListener: Unknown listener base class ' . get_class($listener));
  3998. }
  3999. }
  4000. /**
  4001. * Notify all listeners of the occurance of a certain action.
  4002. *
  4003. * @param String $action The action that occurred
  4004. * @param array $record The record on which the action was performed
  4005. */
  4006. function notify($action, $record)
  4007. {
  4008. for ($i = 0, $_i = count($this->m_actionListeners); $i < $_i; $i++) {
  4009. $this->m_actionListeners[$i]->notify($action, $record);
  4010. }
  4011. }
  4012. /**
  4013. * Notify all listeners in advance of the occurance of a certain action.
  4014. *
  4015. * @param String $action The action that will occur
  4016. * @param array $record The record on which the action will be performed
  4017. */
  4018. function preNotify($action, &$record)
  4019. {
  4020. for ($i = 0, $_i = count($this->m_actionListeners); $i < $_i; $i++) {
  4021. $this->m_actionListeners[$i]->preNotify($action, $record);
  4022. }
  4023. }
  4024. /**
  4025. * Get the column configuration object
  4026. *
  4027. * @param string $id optional column config id
  4028. * @param boolean $forceNew force new instance?
  4029. *
  4030. * @return atkColumnConfig
  4031. */
  4032. function &getColumnConfig($id = NULL, $forceNew = false)
  4033. {
  4034. $columnConfig = Adapto_Recordlist_ColumnConfig::getConfig($this, $id, $forceNew);
  4035. return $columnConfig;
  4036. }
  4037. /**
  4038. * Translate using this entity's module and type.
  4039. *
  4040. * @param mixed $string string or array of strings containing the name(s) of the string to return
  4041. * when an array of strings is passed, the second will be the fallback if
  4042. * the first one isn't found, and so forth
  4043. * @param String $module module in which the language file should be looked for,
  4044. * defaults to core module with fallback to ATK
  4045. * @param String $lng ISO 639-1 language code, defaults to config variable
  4046. * @param String $firstfallback the first module to check as part of the fallback
  4047. * @param boolean $entityfaulttext if true, then it doesn't return a default text
  4048. * when it can't find a translation
  4049. * @return String the string from the languagefile
  4050. */
  4051. function text($string, $module = NULL, $lng = "", $firstfallback = "", $entityfaulttext = false)
  4052. {
  4053. if ($module == NULL)
  4054. $module = $this->m_module;
  4055. return atktext($string, $module, $this->m_type, $lng, $firstfallback, $entityfaulttext);
  4056. }
  4057. /**
  4058. * String representation for this entity (PHP5 only).
  4059. *
  4060. * @return string ATK entity type
  4061. */
  4062. function __toString()
  4063. {
  4064. return $this->atkEntityType();
  4065. }
  4066. /**
  4067. * Set the edit fieldprefix to use in atk
  4068. *
  4069. * @param string $prefix
  4070. */
  4071. function setEditFieldPrefix($prefix)
  4072. {
  4073. $this->m_edit_fieldprefix = $prefix;
  4074. }
  4075. /**
  4076. * Get the edit fieldprefix to use
  4077. *
  4078. * @param boolean $Adapto_layout do we want the prefix in atkstyle (with _AE_) or not
  4079. * @return string with edit fieldprefix
  4080. */
  4081. function getEditFieldPrefix($Adapto_layout = true)
  4082. {
  4083. if ($this->m_edit_fieldprefix == '')
  4084. return '';
  4085. else
  4086. return $this->m_edit_fieldprefix . ($Adapto_layout ? '_AE_' : '');
  4087. }
  4088. /**
  4089. * Escape SQL string, uses the entity's database to do the escaping.
  4090. *
  4091. * @param string $string string to escape
  4092. *
  4093. * @return string escaped string
  4094. */
  4095. public function escapeSQL($string)
  4096. {
  4097. return $this->getDb()->escapeSQL($string);
  4098. }
  4099. /**
  4100. * Row CSS class.
  4101. *
  4102. * Used to determine the CSS class(s) for rows in the datagrid list.
  4103. *
  4104. * @param array $record record
  4105. * @param int $nr row number
  4106. *
  4107. * @return string CSS class(es)
  4108. */
  4109. public function rowClass($record, $nr)
  4110. {
  4111. return $nr % 2 == 0 ? 'row1' : 'row2';
  4112. }
  4113. /**
  4114. * Add callback function for add css class to row.
  4115. *
  4116. * @param mixed $callback name of a function or array with an object
  4117. * and the name of a method or closure
  4118. *
  4119. * @return boolean
  4120. */
  4121. public function setRowClassCallback($callback)
  4122. {
  4123. $res = false;
  4124. if (is_callable($callback, false, $callableName)) {
  4125. if (is_array($callback) && !method_exists($callback[0], $callback[1])) {
  4126. throw new Adapto_Exception("The registered row class callback method '$callableName' doesn't exist");
  4127. } else {
  4128. $this->m_rowClassCallback[] = $callback;
  4129. $res = true;
  4130. }
  4131. } else {
  4132. if (is_array($callback)) {
  4133. if (!method_exists($callback[0], $callback[1])) {
  4134. throw new Adapto_Exception("The registered row class callback method '$callableName' doesn't exist");
  4135. }
  4136. }
  4137. throw new Adapto_Exception("The registered row class callback '$callableName' is not callable");
  4138. }
  4139. return $res;
  4140. }
  4141. /**
  4142. * Return array with callback function list, which use for add css class to row
  4143. *
  4144. * @return array
  4145. */
  4146. public function getRowClassCallback()
  4147. {
  4148. return $this->m_rowClassCallback;
  4149. }
  4150. }
  4151. ?>