PageRenderTime 73ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/PluggableAuth/includes/PHPDS_importUser.class.php

https://github.com/TitanKing/todoplugins
PHP | 783 lines | 387 code | 86 blank | 310 comment | 57 complexity | d8b9223e92517a220f258fc18cd1ad8f MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /**
  3. <<<<<<< TREE
  4. * PHPDevShell is a RAD Framework aimed at developing administrative applications.
  5. *
  6. * @package PHPDevShell
  7. * @link http://www.phpdevshell.org
  8. * @copyright Copyright (C) 2007 Jason Schoeman, All rights reserved.
  9. * @license GNU/LGPL, see readme/licensed_under_lgpl or http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
  10. *
  11. * Copyright notice: See readme/notice
  12. * By using PHPDevShell you agree to notice and license, if you dont agree to this notice/license you are not allowed to use PHPDevShell.
  13. *
  14. */
  15. require_once dirname(__FILE__).'/../models/PHPDS_importUser.query.php';
  16. /**
  17. =======
  18. >>>>>>> MERGE-SOURCE
  19. * A class representing a user in PHPDevShell.
  20. *
  21. * Can be used to examine / modify an existing user, or create / import a new user
  22. *
  23. * @date 20091125
  24. * @version 1.0.1
  25. * @author Greg
  26. */
  27. class PHPDS_importUser extends PHPDS_user
  28. {
  29. //for flexibily all user data are stored in an array
  30. protected $data = array(); // main table
  31. protected $data_ex = array(); // overflow table
  32. protected $ready = false; // if the user object is actually ready to be used (i.e. fields are valid)
  33. protected $userid = false; // if the user is know to exist in the database, here is his user ID
  34. protected $dirty = false; // if the user data in memory are different from the user data in the database
  35. protected $tokenid = false; // token to authorize the user
  36. protected $overflowtable = false; // table to hold extended data
  37. protected $primary_group = null;
  38. protected $primary_role = null;
  39. protected $extra_groups = array(); // associative array: [groupID] = true
  40. protected $extra_roles = array(); // associative array: [roleID] = true
  41. protected $lookupQuery; // PHPDS_FindUsersQuery
  42. /**
  43. * Constructor
  44. *
  45. * Initialize a User object and optionnaly tries to load a given user's data
  46. *
  47. * @version 1.0.2
  48. * @author greg
  49. * @date 20100921 (v1.0.2) (greg) moved to new style constructor, removing the $dependancy parameter
  50. * @date 20100412 (v1.0.1) (greg) removed tokenid
  51. * @date 20091126 created
  52. * @return the user obejct
  53. */
  54. public function construct($username = null)
  55. {
  56. $this->init();
  57. if ($username) $this->load($username);
  58. return $this;
  59. }
  60. /**
  61. * Magic getter
  62. * Used to directly access (read only) the user's data
  63. *
  64. * @version 1.0
  65. * @date 20091126 created
  66. * @see current/includes/PHPDS_dependant#__get($name)
  67. */
  68. public function __get($field)
  69. {
  70. if (isset($this->data[$field])) return $this->data[$field];
  71. if (isset($this->data_ex[$field])) return $this->data_ex[$field];
  72. return parent::__get($field);
  73. }
  74. /**
  75. * Initialize the object with some default values
  76. *
  77. * For all load/import methods, this is what the new data will be merge onto
  78. *
  79. * @version 1.0
  80. * @date 20091126 created
  81. * @return the user obejct
  82. */
  83. public function init()
  84. {
  85. $this->data = array();
  86. $this->data_ex = array();
  87. $this->extra_groups = array();
  88. $this->extra_roles = array();
  89. $this->ready = false;
  90. $this->dirty = false;
  91. $this->userid = false;
  92. // these fields are to be filled
  93. $this->data['user_id'] = 0;
  94. $this->data['user_name'] = _('MISSING???');
  95. $this->data['user_display_name'] = _('MISSING???');
  96. $this->data['user_email'] = _('MISSING???');
  97. $this->data['user_password'] = 'password';
  98. $settings = $this->db->getSettings(array('registration_group', 'registration_role'), 'PHPDevShell');
  99. // these fields have default values
  100. //$this->PHPDS_dependance->copyArray($settings, &$this->data, array('registration_group'));
  101. $this->data['user_group'] = $settings['registration_group'];
  102. $this->data['user_role'] = $settings['registration_role'];
  103. $this->data['date_registered'] = $this->configuration['time'];
  104. $this->data['language'] = $this->configuration['language'];
  105. if (isset($this->configuration['user_timezone'])) {
  106. $this->data['user_timezone'] = $this->configuration['user_timezone'];
  107. $this->data['timezone'] = $this->configuration['user_timezone'];
  108. } else {
  109. $this->data['user_timezone'] = '';
  110. $this->data['timezone'] = '';
  111. }
  112. $this->data['region'] = $this->configuration['region'];
  113. return $this;
  114. }
  115. /**
  116. * Set/Get the user ID
  117. *
  118. * @version 1.0
  119. * @date 20091126 created
  120. * @author greg
  121. * @param $userID (optional)
  122. * @return integer the user ID
  123. */
  124. protected function ID($userID = null)
  125. {
  126. $ID = intval($userID);
  127. if ($ID > 0) {
  128. $this->userid = $ID;
  129. $this->data['user_id'] = $ID;
  130. }
  131. return $this->userid;
  132. }
  133. /**
  134. * Return an associative array with all user data
  135. *
  136. * @version 1.0
  137. * @author greg
  138. * @return array
  139. */
  140. public function data()
  141. {
  142. return array_merge($this->data, $this->data_ex);
  143. }
  144. /**
  145. *
  146. * @return PHPDS_query
  147. */
  148. protected function lookupQuery()
  149. {
  150. if (empty($this->lookupQuery)) {
  151. $this->lookupQuery = $this->db->makeQuery('PHPDS_FindUserQuery');
  152. }
  153. return $this->lookupQuery;
  154. }
  155. /**
  156. * Lookup a name in the database, optionaly loading the data
  157. *
  158. * Note: if the data is loaded, the object is reset before importing. If the data is not loaded, the object stays untouched
  159. *
  160. * @version 1.0
  161. * @date 20091126 created
  162. * @author greg
  163. * @param $username (optional) "user_name" to lookup (if empty, taken from the user's data)
  164. * @param $load (optional, default is not to load) boolean, do we load the data into the object
  165. * @return integer the user ID (or false if not found)
  166. */
  167. public function lookup($username = null, $load = false)
  168. {
  169. $ID = false;
  170. $exists = false;
  171. $existing_user = $this->lookupQuery()->invoke($username);
  172. $exists = !empty($existing_user['user_id']);
  173. if ($exists) $ID = intval($existing_user['user_id']);
  174. if ($exists && $load) {
  175. $this->init();
  176. $this->ID($existing_user['user_id']);
  177. $this->importArray($existing_user);
  178. $this->primary_group = $existing_user['user_group'];
  179. $this->primary_role = $existing_user['user_role'];
  180. $groups = $this->db->invokeQuery('PHPDS_UserGroupsQuery', $ID);
  181. if ($groups)
  182. foreach ($groups as $group)
  183. $this->addGroup($group['user_group_id']);
  184. $roles = $this->db->invokeQuery('PHPDS_UserRolesQuery', $ID);
  185. if ($roles)
  186. foreach ($roles as $role)
  187. $this->addRole($role['user_role_id']);
  188. $this->ready = true;
  189. $this->dirty = false;
  190. }
  191. $this->log('Looking up user "'.$username.'" gave id '.$ID);
  192. return $ID;
  193. }
  194. /**
  195. * Add the user to a group
  196. *
  197. * @param $groupID
  198. * @return unknown_type
  199. */
  200. public function addGroup($groupID)
  201. {
  202. //array_push($this->extra_groups, intval($groupID));
  203. $this->extra_groups[intval($groupID)] = true;
  204. }
  205. public function removeGroup($groupID)
  206. {
  207. //array_push($this->extra_groups, intval($groupID));
  208. unset($this->extra_groups[intval($groupID)]);
  209. }
  210. /**
  211. * Return an array of the roles given to the user
  212. *
  213. * @return unknown_type
  214. */
  215. public function roles()
  216. {
  217. return array_keys($this->extra_roles);
  218. }
  219. /**
  220. * Return an array of the possible roles for the user
  221. *
  222. * @return unknown_type
  223. */
  224. public function possibleRoles()
  225. {
  226. return $this->db->invokeQuery('PHPDS_RolesQuery');
  227. }
  228. /**
  229. * Return an array of the groups the user belongs to
  230. *
  231. * @return unknown_type
  232. */
  233. public function groups()
  234. {
  235. return array_keys($this->extra_groups);
  236. }
  237. /**
  238. * Add a role to the user
  239. *
  240. * @param $roleID
  241. * @return unknown_type
  242. */
  243. public function addRole($roleID)
  244. {
  245. $this->extra_roles[intval($roleID)] = true;
  246. }
  247. /**
  248. * Import some data from an array into the object
  249. *
  250. * @version 1.0
  251. * @date 20091126 created
  252. * @param $import_data associative array of user data
  253. * @return the user object
  254. */
  255. public function importArray(array $import_data)
  256. {
  257. $this->ready = false;
  258. foreach ($import_data as $field => $value) {
  259. if (isset($this->data[$field])) $this->data[$field] = $value;
  260. else $this->data_ex[$field] = $value;
  261. }
  262. $this->dirty = true;
  263. $this->ready = true;
  264. return $this;
  265. }
  266. public function import(array $import_array)
  267. {
  268. $this->ready = false;
  269. foreach ($import_array as $field => $value) {
  270. $import_data[$field] = $this->valuePrep($field, $value);
  271. }
  272. $this->importPrep($import_data);
  273. return $this;
  274. }
  275. /**
  276. * Load a user from the database into the object
  277. *
  278. * @version 1.0.1
  279. * @date 20100412 (v1.0.1) (greg) $username is optional
  280. * @date 20091126 created
  281. * @author greg
  282. * @param $username (optional) "user_name" to lookup (if empty, taken from the user's data)
  283. * @return integer the user ID (or false if not found)
  284. */
  285. public function load($username = null)
  286. {
  287. //$this->init();
  288. if (!$username && isset($this->data['user_name']))
  289. $username = $this->data['user_name'];
  290. return $this->lookup($username, true);
  291. }
  292. /**
  293. * Save the user object into the database
  294. *
  295. * @version 1.1
  296. * @date 20091126 created
  297. * @date 20100329 heavily modified
  298. * @return unknown_type
  299. */
  300. public function save()
  301. {
  302. if ($this->userid) {
  303. $existing_user = $this->userid;
  304. } else {
  305. if (isset($this->data['user_name'])) {
  306. $existing_user = $this->lookup(array('user_name' => $this->data['user_name']));
  307. }
  308. }
  309. if (empty($existing_user)) $existing_user = 'NULL';
  310. $this->log('About to save user id '.$existing_user.' from id '.$this->userid.' / name '.$this->data['user_name']);
  311. $db = $this->db;
  312. $user_id = $db->invokeQueryWith('PHPDS_UserReplace', array_merge($this->data, array('existing_user' => $existing_user)));
  313. $this->log('Saving user id '.$user_id);
  314. $db->invokeQuery('PHPDS_UserSetRolesQuery', $user_id, array_keys($this->extra_roles));
  315. $db->invokeQuery('PHPDS_UserSetGroupsQuery', $user_id, array_keys($this->extra_groups));
  316. // TODO
  317. if (empty($this->overflowtable)) $this->log('No overflow table'); else $this->log('Overflow table is '.$this->overflowtable);
  318. if (!empty($this->overflowtable) && !empty($this->data_ex)) {
  319. $overflow_values = false;
  320. foreach ($this->data_ex as $custom_column => $custom_column_value) {
  321. $custom_column_value_ = $this->db->protect(str_replace(array("\n", "\r", "\r\n", "\n\r", ",", ";", "'", '"'), '', trim($custom_column_value)));
  322. $overflow_values .= "$custom_column = '$custom_column_value_',";
  323. }
  324. $overflow_values = rtrim($overflow_values, ",");
  325. $sql = "REPLACE INTO
  326. {$this->overflowtable}
  327. SET
  328. user_id =$user_id, $overflow_values";
  329. $this->db->newQuery($sql);
  330. }
  331. $this->ID($user_id);
  332. return $this;
  333. }
  334. /**
  335. * Import some data from a csv-like string into the object
  336. *
  337. * Note: the object is NOT reset before importing
  338. *
  339. * @version 1.0
  340. * @date 20091126 created
  341. * @param $line string, the line to import
  342. * @param $fields array, the (ordered) names of the fields
  343. * @param $delimiter (optional) string, the csv delimiter
  344. * @return boolean false if the line doesn't contains as much fields as the $fields array
  345. */
  346. public function importCsvLine($line, $fields, $delimiter = "\t")
  347. {
  348. $values = explode($delimiter, $line);
  349. if ($import = array_combine($fields, $values)) {
  350. $this->import($import);
  351. return true;
  352. } else return false;
  353. }
  354. /**
  355. * Import several csv-like lines, and save the corresponding user for each line
  356. *
  357. * Use $this->import_prep() as a callback for each line
  358. *
  359. * @param $lines array of strings, the lines to import
  360. * @param $fields array, the (ordered) names of the fields
  361. * @param $delimiter (optional) string, the csv delimiter
  362. */
  363. public function importCsv($lines, $fields, $delimiter = "\t")
  364. {
  365. foreach ($lines as $line) {
  366. $this->init();
  367. $this->importCsvLine($line, $fields, $delimiter);
  368. if ($this->dirty) $this->save();
  369. }
  370. }
  371. /**
  372. * Import a whole csv-like file into the database
  373. *
  374. * Use $this->import_prep() as a callback for each line
  375. *
  376. * @param $filename full pathname of the file
  377. * @param $fields array, the (ordered) names of the fields
  378. * @param $delimiter (optional) string, the csv delimiter
  379. * @return unknown_type
  380. */
  381. public function importCsvFile($filename, $fields, $delimiter = "\t")
  382. {
  383. $content = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES | FILE_TEXT);
  384. if ($content) return $this->importCsv($content, $fields, $delimiter);
  385. else return false;
  386. }
  387. /**
  388. * This function will be called for each line when importing a file. It's meant to be overriden.
  389. * Here is a very simple example.
  390. *
  391. * No paramaters or return value: everything is done with the object's data
  392. *
  393. * Note 1: it's currently only used when importing csv data
  394. * Note 2: values have already been "prepared"
  395. */
  396. public function importPrep($import_data)
  397. {
  398. if (($import_data['user_display_name'] == _('MISSING???')) || empty($import_data['user_display_name'])) {
  399. $import_data['user_display_name'] = $import_data['user_name'];
  400. }
  401. $this->importArray($import_array);
  402. return $this;
  403. }
  404. /**
  405. * This function is called for each value when importing data
  406. *
  407. * Its goal is to "prepare" a value before actually importing it
  408. * This basic implementation just cleans values which are surrounded by double quotes. You are supposed to override it according to your data format
  409. *
  410. * @param $field name of the field containing the value
  411. * @param $value the value to prepare
  412. * @return mixed, the prepare valued
  413. */
  414. public function valuePrep($field, $value)
  415. {
  416. $matches = array();
  417. if (preg_match('/"(.*)"/', $value, $matches)) {
  418. $value = $matches[1];
  419. }
  420. return $value;
  421. }
  422. /**
  423. * Dump the object content
  424. *
  425. * @return unknown_type
  426. */
  427. public function dump()
  428. {
  429. print "\nUserID ";
  430. print_r($this->userid);
  431. print "\ndirty ";
  432. print_r($this->dirty);
  433. print "\nread ";
  434. print_r($this->ready);
  435. print "\ndata ";
  436. print_r($this->data);
  437. print "\ndata_ex ";
  438. print_r($this->data_ex);
  439. print "\nprimary_group ";
  440. print_r($this->primary_group);
  441. print "\nextra_groups ";
  442. print_r(array_keys($this->extra_groups));
  443. print "\nprimary_role ";
  444. print_r($this->primary_role);
  445. print "\nextra_roles ";
  446. print_r(array_keys($this->extra_roles));
  447. }
  448. /**
  449. *
  450. * @param <type> $name
  451. * @param <type> $value
  452. */
  453. public function __set($name, $value)
  454. {
  455. $setter = 'set_' . $name;
  456. // if there is an explicit method to set this value, let's call it
  457. if (method_exists($this, $setter)) $this->$setter($value);
  458. else {
  459. // no explicit method, so let's try what we can do
  460. if (isset($this->data[$name])) $this->data[$name] = $value;
  461. elseif (isset($this->data['user_' . $name]))
  462. $this->data['user_' . $name] = $value;
  463. else $this->data_ex[$name] = $value;
  464. }
  465. }
  466. /**
  467. *
  468. * @param <type> $password
  469. * @return PHPDS_user
  470. */
  471. public function setPassword($password)
  472. {
  473. $this->data['user_password'] = md5($password);
  474. return $this;
  475. }
  476. /**
  477. * Lookup all users which are members of any of the current user's groups
  478. *
  479. * @return array
  480. */
  481. public function siblings()
  482. {
  483. return $this->db->invokeQuery('PHPDS_FindUsersQuery');
  484. }
  485. }
  486. /**
  487. * A specific tree for groups handling
  488. *
  489. * Usage:
  490. *
  491. * $tree = PHPDS_GroupTree::singleton($dependance);
  492. * echo '<form action="" method="post"><ul id="example">';
  493. * echo $tree->make_html();
  494. * echo '<input type="submit" name="submit" value="Send" .>';
  495. * echo '</ul></form>';
  496. *
  497. * Can also be used to find a subgroup for a query:
  498. *
  499. $this->where = $tree->user_groups_sql(35); // group 35 and all groups inside it
  500. * Note: it fit in the PHPDS_dependant system
  501. *
  502. * @author greg
  503. *
  504. */
  505. class PHPDS_groupTree extends PU_tree
  506. {
  507. protected static $instance;
  508. protected static $dependance;
  509. /** constructor is private to ensure singleton
  510. *
  511. * @param $dependance
  512. * @return irrelevant
  513. */
  514. private function __construct($dependance)
  515. {
  516. $this->dependance = $dependance;
  517. }
  518. /**
  519. * Return the single instance
  520. *
  521. * @param PHPDS_dependant $dependance
  522. * @return PHPDS_GroupTree
  523. */
  524. public static function singleton($dependance = null)
  525. {
  526. if (!isset(PHPDS_groupTree::$instance)) {
  527. PHPDS_groupTree::$instance = new PHPDS_groupTree($dependance);
  528. }
  529. return PHPDS_groupTree::$instance;
  530. }
  531. /**
  532. * Load (only once) all the groups into the tree
  533. *
  534. * @return this
  535. */
  536. public function load()
  537. {
  538. if (empty($this->descendants)) {
  539. $groups = $this->dependance->db->invokeQuery('PHPDS_AllGroupsQuery');
  540. if (false == $groups) throw new Exception('Error loading group list');
  541. foreach ($groups as $group) {
  542. $this->add($group['user_group_id'], $group['parent_group_id'], $group['user_group_name']);
  543. }
  544. $this->climb();
  545. }
  546. return $this;
  547. }
  548. /**
  549. * Build an html representation of the groups selector (including INPUTs)
  550. *
  551. * Result is something like:
  552. *
  553. * <ul>
  554. * <li>
  555. * <input type="checkbox" id="group_2" value="2" checked /><label for="group_2">Registered<label>
  556. * <ul>
  557. * <li>
  558. * <input type="checkbox" id="group_6" value="6" checked /><label for="group_6">Clients<label><input type="checkbox" id="group_7" value="7" checked />
  559. * </li>
  560. * </ul>
  561. * </li>
  562. * </ul>
  563. *
  564. * @param integer $branch starting branch (optional, usually 0)
  565. * @return string html
  566. */
  567. public function makeHtml($branch = 0)
  568. {
  569. $this->load();
  570. $cuts = $this->userGroups($branch, true);
  571. $selected = $cuts;
  572. return $this->makeHtmlBranch($branch, $cuts, $selected);
  573. }
  574. /**
  575. * Sub for the previous function - do not use
  576. *
  577. * @param unknown_type $branch
  578. * @param array $cuts
  579. * @param array $selected
  580. * @param unknown_type $propagate
  581. * @return unknown_type
  582. */
  583. public function makeHtmlBranch($branch, array $cuts, array $selected, $propagate = false)
  584. {
  585. $html1 = '';
  586. $html2 = '';
  587. $propagate = $propagate || in_array($branch, $cuts);
  588. $display = $propagate || (0 == $branch);
  589. if ($display) {
  590. if ($branch) {
  591. $id = 'group_' . $branch;
  592. $html1 .= '<input type="checkbox" id="' . $id . '" ';
  593. if (isset($this->elements[$branch])) $html1 .= ' value="' . $branch . '"';
  594. if (in_array($branch, $selected)) $html1 .= ' checked ';
  595. $html1 .= '/>';
  596. $html1 .= '<label for="' . $id . '">' . $this->elements[$branch] . '<label>';
  597. } else {
  598. $html1 = 'all';
  599. }
  600. }
  601. if (!empty($this->descendants[$branch])) {
  602. foreach ($this->descendants[$branch] as $node)
  603. $html2 .= $this->makeHtmlBranch($node, $cuts, $selected, $propagate);
  604. }
  605. // this is tricky, since we may have to display a group nested inside another group which is not displayed
  606. $html = '';
  607. if ($html1) $html .= "<li>$html1";
  608. if ($html1 && $html2) $html .= '<ul>';
  609. if ($html2) $html .= $html2;
  610. if ($html1 && $html2) $html .= '</ul>';
  611. if ($html1) $html .= "</li>\n";
  612. return $html;
  613. }
  614. /**
  615. * Returns all groups the user belongs to, which are children of given branch
  616. *
  617. * Note: this is used to deal with implicit membership, as we a user of a given group can be considered member of all its subgroups
  618. *
  619. * @param unknown_type $branch
  620. * @param unknown_type $as_array
  621. * @return unknown_type
  622. */
  623. public function userGroups($branch = 0, $as_array = false)
  624. {
  625. $this->load();
  626. $descendants = array_unique($this->descendants($branch, true));
  627. $mygroups = $this->dependance->db->getGroups(false, true);
  628. $groups = array_unique(array_intersect($descendants, $mygroups));
  629. return $as_array ? $groups : implode(',', $groups);
  630. }
  631. /**
  632. * Returns a sql snippet based on the previous method
  633. *
  634. * @param unknown_type $branch
  635. * @return unknown_type
  636. */
  637. public function userGroupsSql($branch = 0)
  638. {
  639. $groups = $this->userGroups($branch, false);
  640. return $groups ? " user_group_id IN ($groups) OR user_group IN ($groups) " : "false";
  641. }
  642. /**
  643. * Find all groups the user belongs to or is a subgroup (a group inside a group the user belongs to)
  644. *
  645. * @see stable/phpdevshell/includes/PU_tree#descendants($node, $as_array)
  646. * @return array of id
  647. */
  648. public function descendants($branch = 0, $as_array = true)
  649. {
  650. $this->load();
  651. $descendants = parent::descendants($branch, true);
  652. $children = $descendants;
  653. foreach ($descendants as $descendant)
  654. $children = array_merge($this->descendants($descendant, true), $children);
  655. $children[] = $branch;
  656. return $as_array ? $children : implode(',', $children);
  657. }
  658. /**
  659. * Find all groups hierachy from the given group up to the top
  660. *
  661. * @see stable/phpdevshell/includes/PU_tree#ascendants($node, $as_array)
  662. * @return array of id
  663. */
  664. public function ascendants($branch = 0, $as_array = true)
  665. {
  666. $this->load();
  667. $elders = parent::ascendants($branch, true);
  668. $elders[] = $branch;
  669. return $as_array ? $elders : implode(',', $elders);
  670. }
  671. /**
  672. * Find all groups hierachy from the given group up to the top
  673. *
  674. * @see stable/phpdevshell/includes/PU_tree#ascendants($node, $as_array)
  675. * @return array of id
  676. */
  677. public function userGroupNames($branch)
  678. {
  679. $nodes = $this->userGroups($branch, true);
  680. return array_intersect_key($this->elements, array_flip($nodes));
  681. }
  682. /**
  683. * Returns an array of names, either the whole tree, or only the groups which IDs are listed in the filter
  684. *
  685. * @param array $filter
  686. * @return array
  687. */
  688. public function nodes(array $filter = null)
  689. {
  690. $this->load();
  691. return parent::nodes($filter);
  692. }
  693. }