PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Varien/Data/Collection/Filesystem.php

https://bitbucket.org/andrewjleavitt/magestudy
PHP | 701 lines | 337 code | 49 blank | 315 comment | 57 complexity | 4574312fc65b08180fdcdba4d0358940 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Varien
  22. * @package Varien_Data
  23. * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Filesystem items collection
  28. *
  29. * Can scan a folder for files and/or folders recursively.
  30. * Creates Varien_Object instance for each item, with its filename and base name
  31. *
  32. * Supports regexp masks that are applied to files and folders base names.
  33. * These masks apply before adding items to collection, during filesystem scanning
  34. *
  35. * Supports dirsFirst feature, that will make directories be before files, regardless of sorting column.
  36. *
  37. * Supports some fancy filters.
  38. *
  39. * At least one target directory must be set
  40. */
  41. class Varien_Data_Collection_Filesystem extends Varien_Data_Collection
  42. {
  43. /**
  44. * Target directory
  45. *
  46. * @var string
  47. */
  48. protected $_targetDirs = array();
  49. /**
  50. * Whether to collect files
  51. *
  52. * @var bool
  53. */
  54. protected $_collectFiles = true;
  55. /**
  56. * Whether to collect directories before files
  57. *
  58. * @var bool
  59. */
  60. protected $_dirsFirst = true;
  61. /**
  62. * Whether to collect recursively
  63. *
  64. * @var bool
  65. */
  66. protected $_collectRecursively = true;
  67. /**
  68. * Whether to collect dirs
  69. *
  70. * @var bool
  71. */
  72. protected $_collectDirs = false;
  73. /**
  74. * Directory names regex pre-filter
  75. *
  76. * @var string
  77. */
  78. protected $_allowedDirsMask = '/^[a-z0-9\.\-\_]+$/i';
  79. /**
  80. * Filenames regex pre-filter
  81. *
  82. * @var string
  83. */
  84. protected $_allowedFilesMask = '/^[a-z0-9\.\-\_]+\.[a-z0-9]+$/i';
  85. /**
  86. * Disallowed filenames regex pre-filter match for better versatility
  87. *
  88. * @var string
  89. */
  90. protected $_disallowedFilesMask = '';
  91. /**
  92. * Filter rendering helper variables
  93. *
  94. * @see Varien_Data_Collection::$_filter
  95. * @see Varien_Data_Collection::$_isFiltersRendered
  96. */
  97. private $_filterIncrement = 0;
  98. private $_filterBrackets = array();
  99. private $_filterEvalRendered = '';
  100. /**
  101. * Collecting items helper variables
  102. *
  103. * @var array
  104. */
  105. protected $_collectedDirs = array();
  106. protected $_collectedFiles = array();
  107. /**
  108. * Allowed dirs mask setter
  109. * Set empty to not filter
  110. *
  111. * @param string $regex
  112. * @return Varien_Data_Collection_Files
  113. */
  114. public function setDirsFilter($regex)
  115. {
  116. $this->_allowedDirsMask = (string)$regex;
  117. return $this;
  118. }
  119. /**
  120. * Allowed files mask setter
  121. * Set empty to not filter
  122. *
  123. * @param string $regex
  124. * @return Varien_Data_Collection_Files
  125. */
  126. public function setFilesFilter($regex)
  127. {
  128. $this->_allowedFilesMask = (string)$regex;
  129. return $this;
  130. }
  131. /**
  132. * Disallowed files mask setter
  133. * Set empty value to not use this filter
  134. *
  135. * @param string $regex
  136. * @return Varien_Data_Collection_Files
  137. */
  138. public function setDisallowedFilesFilter($regex)
  139. {
  140. $this->_disallowedFilesMask = (string)$regex;
  141. return $this;
  142. }
  143. /**
  144. * Set whether to collect dirs
  145. *
  146. * @param bool $value
  147. * @return Varien_Data_Collection_Filesystem
  148. */
  149. public function setCollectDirs($value)
  150. {
  151. $this->_collectDirs = (bool)$value;
  152. return $this;
  153. }
  154. /**
  155. * Set whether to collect files
  156. *
  157. * @param bool $value
  158. * @return Varien_Data_Collection_Filesystem
  159. */
  160. public function setCollectFiles($value)
  161. {
  162. $this->_collectFiles = (bool)$value;
  163. return $this;
  164. }
  165. /**
  166. * Set whether to collect recursively
  167. *
  168. * @param bool $value
  169. * @return Varien_Data_Collection_Filesystem
  170. */
  171. public function setCollectRecursively($value)
  172. {
  173. $this->_collectRecursively = (bool)$value;
  174. return $this;
  175. }
  176. /**
  177. * Target directory setter. Adds directory to be scanned
  178. *
  179. * @param string $value
  180. * @return Varien_Data_Collection_Filesystem
  181. */
  182. public function addTargetDir($value)
  183. {
  184. $value = (string)$value;
  185. if (!is_dir($value)) {
  186. throw new Exception('Unable to set target directory.');
  187. }
  188. $this->_targetDirs[$value] = $value;
  189. return $this;
  190. }
  191. /**
  192. * Set whether to collect directories before files
  193. * Works *before* sorting.
  194. *
  195. * @param bool $value
  196. * @return Varien_Data_Collection_Filesystem
  197. */
  198. public function setDirsFirst($value)
  199. {
  200. $this->_dirsFirst = (bool)$value;
  201. return $this;
  202. }
  203. /**
  204. * Get files from specified directory recursively (if needed)
  205. *
  206. * @param string|array $dir
  207. */
  208. protected function _collectRecursive($dir)
  209. {
  210. $collectedResult = array();
  211. if (!is_array($dir)) {
  212. $dir = array($dir);
  213. }
  214. foreach ($dir as $folder) {
  215. if ($nodes = glob($folder . DIRECTORY_SEPARATOR . '*')) {
  216. foreach ($nodes as $node) {
  217. $collectedResult[] = $node;
  218. }
  219. }
  220. }
  221. if (empty($collectedResult)) {
  222. return;
  223. }
  224. foreach ($collectedResult as $item) {
  225. if (is_dir($item) && (!$this->_allowedDirsMask || preg_match($this->_allowedDirsMask, basename($item)))) {
  226. if ($this->_collectDirs) {
  227. if ($this->_dirsFirst) {
  228. $this->_collectedDirs[] = $item;
  229. }
  230. else {
  231. $this->_collectedFiles[] = $item;
  232. }
  233. }
  234. if ($this->_collectRecursively) {
  235. $this->_collectRecursive($item);
  236. }
  237. }
  238. elseif ($this->_collectFiles && is_file($item)
  239. && (!$this->_allowedFilesMask || preg_match($this->_allowedFilesMask, basename($item)))
  240. && (!$this->_disallowedFilesMask || !preg_match($this->_disallowedFilesMask, basename($item)))) {
  241. $this->_collectedFiles[] = $item;
  242. }
  243. }
  244. }
  245. /**
  246. * Lauch data collecting
  247. *
  248. * @param bool $printQuery
  249. * @param bool $logQuery
  250. * @return Varien_Data_Collection_Filesystem
  251. */
  252. public function loadData($printQuery = false, $logQuery = false)
  253. {
  254. if ($this->isLoaded()) {
  255. return $this;
  256. }
  257. if (empty($this->_targetDirs)) {
  258. throw new Exception('Please specify at least one target directory.');
  259. }
  260. $this->_collectedFiles = array();
  261. $this->_collectedDirs = array();
  262. $this->_collectRecursive($this->_targetDirs);
  263. $this->_generateAndFilterAndSort('_collectedFiles');
  264. if ($this->_dirsFirst) {
  265. $this->_generateAndFilterAndSort('_collectedDirs');
  266. $this->_collectedFiles = array_merge($this->_collectedDirs, $this->_collectedFiles);
  267. }
  268. // calculate totals
  269. $this->_totalRecords = count($this->_collectedFiles);
  270. $this->_setIsLoaded();
  271. // paginate and add items
  272. $from = ($this->getCurPage() - 1) * $this->getPageSize();
  273. $to = $from + $this->getPageSize() - 1;
  274. $isPaginated = $this->getPageSize() > 0;
  275. $cnt = 0;
  276. foreach ($this->_collectedFiles as $row) {
  277. $cnt++;
  278. if ($isPaginated && ($cnt < $from || $cnt > $to)) {
  279. continue;
  280. }
  281. $item = new $this->_itemObjectClass();
  282. $this->addItem($item->addData($row));
  283. if (!$item->hasId()) {
  284. $item->setId($cnt);
  285. }
  286. }
  287. return $this;
  288. }
  289. /**
  290. * With specified collected items:
  291. * - generate data
  292. * - apply filters
  293. * - sort
  294. *
  295. * @param string $attributeName '_collectedFiles' | '_collectedDirs'
  296. */
  297. private function _generateAndFilterAndSort($attributeName)
  298. {
  299. // generate custom data (as rows with columns) basing on the filenames
  300. foreach ($this->$attributeName as $key => $filename) {
  301. $this->{$attributeName}[$key] = $this->_generateRow($filename);
  302. }
  303. // apply filters on generated data
  304. if (!empty($this->_filters)) {
  305. foreach ($this->$attributeName as $key => $row) {
  306. if (!$this->_filterRow($row)) {
  307. unset($this->{$attributeName}[$key]);
  308. }
  309. }
  310. }
  311. // sort (keys are lost!)
  312. if (!empty($this->_orders)) {
  313. usort($this->$attributeName, array($this, '_usort'));
  314. }
  315. }
  316. /**
  317. * Callback for sorting items
  318. * Currently supports only sorting by one column
  319. *
  320. * @param array $a
  321. * @param array $b
  322. * @return int
  323. */
  324. protected function _usort($a, $b)
  325. {
  326. foreach ($this->_orders as $key => $direction) {
  327. $result = $a[$key] > $b[$key] ? 1 : ($a[$key] < $b[$key] ? -1 : 0);
  328. return (self::SORT_ORDER_ASC === strtoupper($direction) ? $result : -$result);
  329. break;
  330. }
  331. }
  332. /**
  333. * Set select order
  334. * Currently supports only sorting by one column
  335. *
  336. * @param string $field
  337. * @param string $direction
  338. * @return Varien_Data_Collection
  339. */
  340. public function setOrder($field, $direction = self::SORT_ORDER_DESC)
  341. {
  342. $this->_orders = array($field => $direction);
  343. return $this;
  344. }
  345. /**
  346. * Generate item row basing on the filename
  347. *
  348. * @param string $filename
  349. * @return array
  350. */
  351. protected function _generateRow($filename)
  352. {
  353. return array(
  354. 'filename' => $filename,
  355. 'basename' => basename($filename),
  356. );
  357. }
  358. /**
  359. * Set a custom filter with callback
  360. * The callback must take 3 params:
  361. * string $field - field key,
  362. * mixed $filterValue - value to filter by,
  363. * array $row - a generated row (before generaring varien objects)
  364. *
  365. * @param string $field
  366. * @param mixed $value
  367. * @param string $type 'and'|'or'
  368. * @param callback $callback
  369. * @param bool $isInverted
  370. * @return Varien_Data_Collection_Filesystem
  371. */
  372. public function addCallbackFilter($field, $value, $type, $callback, $isInverted = false)
  373. {
  374. $this->_filters[$this->_filterIncrement] = array(
  375. 'field' => $field,
  376. 'value' => $value,
  377. 'is_and' => 'and' === $type,
  378. 'callback' => $callback,
  379. 'is_inverted' => $isInverted
  380. );
  381. $this->_filterIncrement++;
  382. return $this;
  383. }
  384. /**
  385. * The filters renderer and caller
  386. * Aplies to each row, renders once.
  387. *
  388. * @param array $row
  389. * @return bool
  390. */
  391. protected function _filterRow($row)
  392. {
  393. // render filters once
  394. if (!$this->_isFiltersRendered) {
  395. $eval = '';
  396. for ($i = 0; $i < $this->_filterIncrement; $i++) {
  397. if (isset($this->_filterBrackets[$i])) {
  398. $eval .= $this->_renderConditionBeforeFilterElement($i, $this->_filterBrackets[$i]['is_and'])
  399. . $this->_filterBrackets[$i]['value'];
  400. }
  401. else {
  402. $f = '$this->_filters[' . $i . ']';
  403. $eval .= $this->_renderConditionBeforeFilterElement($i, $this->_filters[$i]['is_and'])
  404. . ($this->_filters[$i]['is_inverted'] ? '!' : '')
  405. . '$this->_invokeFilter(' . "{$f}['callback'], array({$f}['field'], {$f}['value'], " . '$row))';
  406. }
  407. }
  408. $this->_filterEvalRendered = $eval;
  409. $this->_isFiltersRendered = true;
  410. }
  411. $result = false;
  412. if ($this->_filterEvalRendered) {
  413. eval('$result = ' . $this->_filterEvalRendered . ';');
  414. }
  415. return $result;
  416. }
  417. /**
  418. * Invokes specified callback
  419. * Skips, if there is no filtered key in the row
  420. *
  421. * @param callback $callback
  422. * @param array $callbackParams
  423. * @return bool
  424. */
  425. protected function _invokeFilter($callback, $callbackParams)
  426. {
  427. list($field, $value, $row) = $callbackParams;
  428. if (!array_key_exists($field, $row)) {
  429. return false;
  430. }
  431. return call_user_func_array($callback, $callbackParams);
  432. }
  433. /**
  434. * Fancy field filter
  435. *
  436. * @param string $field
  437. * @param mixed $cond
  438. * @param string $type 'and' | 'or'
  439. * @see Varien_Data_Collection_Db::addFieldToFilter()
  440. * @return Varien_Data_Collection_Filesystem
  441. */
  442. public function addFieldToFilter($field, $cond, $type = 'and')
  443. {
  444. $inverted = true;
  445. // simply check whether equals
  446. if (!is_array($cond)) {
  447. return $this->addCallbackFilter($field, $cond, $type, array($this, 'filterCallbackEq'));
  448. }
  449. // versatile filters
  450. if (isset($cond['from']) || isset($cond['to'])) {
  451. $this->_addFilterBracket('(', 'and' === $type);
  452. if (isset($cond['from'])) {
  453. $this->addCallbackFilter($field, $cond['from'], 'and', array($this, 'filterCallbackIsLessThan'), $inverted);
  454. }
  455. if (isset($cond['to'])) {
  456. $this->addCallbackFilter($field, $cond['to'], 'and', array($this, 'filterCallbackIsMoreThan'), $inverted);
  457. }
  458. return $this->_addFilterBracket(')');
  459. }
  460. if (isset($cond['eq'])) {
  461. return $this->addCallbackFilter($field, $cond['eq'], $type, array($this, 'filterCallbackEq'));
  462. }
  463. if (isset($cond['neq'])) {
  464. return $this->addCallbackFilter($field, $cond['neq'], $type, array($this, 'filterCallbackEq'), $inverted);
  465. }
  466. if (isset($cond['like'])) {
  467. return $this->addCallbackFilter($field, $cond['like'], $type, array($this, 'filterCallbackLike'));
  468. }
  469. if (isset($cond['nlike'])) {
  470. return $this->addCallbackFilter($field, $cond['nlike'], $type, array($this, 'filterCallbackLike'), $inverted);
  471. }
  472. if (isset($cond['in'])) {
  473. return $this->addCallbackFilter($field, $cond['in'], $type, array($this, 'filterCallbackInArray'));
  474. }
  475. if (isset($cond['nin'])) {
  476. return $this->addCallbackFilter($field, $cond['nin'], $type, array($this, 'filterCallbackIn'), $inverted);
  477. }
  478. if (isset($cond['notnull'])) {
  479. return $this->addCallbackFilter($field, $cond['notnull'], $type, array($this, 'filterCallbackIsNull'), $inverted);
  480. }
  481. if (isset($cond['null'])) {
  482. return $this->addCallbackFilter($field, $cond['null'], $type, array($this, 'filterCallbackIsNull'));
  483. }
  484. if (isset($cond['moreq'])) {
  485. return $this->addCallbackFilter($field, $cond['moreq'], $type, array($this, 'filterCallbackIsLessThan'), $inverted);
  486. }
  487. if (isset($cond['gt'])) {
  488. return $this->addCallbackFilter($field, $cond['gt'], $type, array($this, 'filterCallbackIsMoreThan'));
  489. }
  490. if (isset($cond['lt'])) {
  491. return $this->addCallbackFilter($field, $cond['lt'], $type, array($this, 'filterCallbackIsLessThan'));
  492. }
  493. if (isset($cond['gteq'])) {
  494. return $this->addCallbackFilter($field, $cond['gteq'], $type, array($this, 'filterCallbackIsLessThan'), $inverted);
  495. }
  496. if (isset($cond['lteq'])) {
  497. return $this->addCallbackFilter($field, $cond['lteq'], $type, array($this, 'filterCallbackIsMoreThan'), $inverted);
  498. }
  499. if (isset($cond['finset'])) {
  500. $filterValue = ($cond['finset'] ? explode(',', $cond['finset']) : array());
  501. return $this->addCallbackFilter($field, $filterValue, $type, array($this, 'filterCallbackInArray'));
  502. }
  503. // add OR recursively
  504. foreach ($cond as $orCond) {
  505. $this->_addFilterBracket('(', 'and' === $type);
  506. $this->addFieldToFilter($field, $orCond, 'or');
  507. $this->_addFilterBracket(')');
  508. }
  509. return $this;
  510. }
  511. /**
  512. * Prepare a bracket into filters
  513. *
  514. * @param string $bracket
  515. * @param bool $isAnd
  516. * @return Varien_Data_Collection_Filesystem
  517. */
  518. protected function _addFilterBracket($bracket = '(', $isAnd = true)
  519. {
  520. $this->_filterBrackets[$this->_filterIncrement] = array(
  521. 'value' => $bracket === ')' ? ')' : '(',
  522. 'is_and' => $isAnd,
  523. );
  524. $this->_filterIncrement++;
  525. return $this;
  526. }
  527. /**
  528. * Render condition sign before element, if required
  529. *
  530. * @param int $increment
  531. * @param bool $isAnd
  532. * @return string
  533. */
  534. protected function _renderConditionBeforeFilterElement($increment, $isAnd)
  535. {
  536. if (isset($this->_filterBrackets[$increment]) && ')' === $this->_filterBrackets[$increment]['value']) {
  537. return '';
  538. }
  539. $prevIncrement = $increment - 1;
  540. $prevBracket = false;
  541. if (isset($this->_filterBrackets[$prevIncrement])) {
  542. $prevBracket = $this->_filterBrackets[$prevIncrement]['value'];
  543. }
  544. if ($prevIncrement < 0 || $prevBracket === '(') {
  545. return '';
  546. }
  547. return ($isAnd ? ' && ' : ' || ');
  548. }
  549. /**
  550. * Does nothing. Intentionally disabled parent method
  551. *
  552. * @return Varien_Data_Collection_Filesystem
  553. */
  554. public function addFilter($field, $value, $type = 'and')
  555. {
  556. return $this;
  557. }
  558. /**
  559. * Get all ids of collected items
  560. *
  561. * @return array
  562. */
  563. public function getAllIds()
  564. {
  565. return array_keys($this->_items);
  566. }
  567. /**
  568. * Callback method for 'like' fancy filter
  569. *
  570. * @param string $field
  571. * @param mixed $filterValue
  572. * @param array $row
  573. * @return bool
  574. * @see addFieldToFilter()
  575. * @see addCallbackFilter()
  576. */
  577. public function filterCallbackLike($field, $filterValue, $row)
  578. {
  579. $filterValueRegex = str_replace('%', '(.*?)', preg_quote($filterValue, '/'));
  580. return (bool)preg_match("/^{$filterValueRegex}$/i", $row[$field]);
  581. }
  582. /**
  583. * Callback method for 'eq' fancy filter
  584. *
  585. * @param string $field
  586. * @param mixed $filterValue
  587. * @param array $row
  588. * @return bool
  589. * @see addFieldToFilter()
  590. * @see addCallbackFilter()
  591. */
  592. public function filterCallbackEq($field, $filterValue, $row)
  593. {
  594. return $filterValue == $row[$field];
  595. }
  596. /**
  597. * Callback method for 'in' fancy filter
  598. *
  599. * @param string $field
  600. * @param mixed $filterValue
  601. * @param array $row
  602. * @return bool
  603. * @see addFieldToFilter()
  604. * @see addCallbackFilter()
  605. */
  606. public function filterCallbackInArray($field, $filterValue, $row)
  607. {
  608. return in_array($row[$field], $filterValue);
  609. }
  610. /**
  611. * Callback method for 'isnull' fancy filter
  612. *
  613. * @param string $field
  614. * @param mixed $filterValue
  615. * @param array $row
  616. * @return bool
  617. * @see addFieldToFilter()
  618. * @see addCallbackFilter()
  619. */
  620. public function filterCallbackIsNull($field, $filterValue, $row)
  621. {
  622. return null === $row[$field];
  623. }
  624. /**
  625. * Callback method for 'moreq' fancy filter
  626. *
  627. * @param string $field
  628. * @param mixed $filterValue
  629. * @param array $row
  630. * @return bool
  631. * @see addFieldToFilter()
  632. * @see addCallbackFilter()
  633. */
  634. public function filterCallbackIsMoreThan($field, $filterValue, $row)
  635. {
  636. return $row[$field] > $filterValue;
  637. }
  638. /**
  639. * Callback method for 'lteq' fancy filter
  640. *
  641. * @param string $field
  642. * @param mixed $filterValue
  643. * @param array $row
  644. * @return bool
  645. * @see addFieldToFilter()
  646. * @see addCallbackFilter()
  647. */
  648. public function filterCallbackIsLessThan($field, $filterValue, $row)
  649. {
  650. return $row[$field] < $filterValue;
  651. }
  652. }