PageRenderTime 73ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/class.atknode.inc

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