Library.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\console\command;
  9. use Phar;
  10. use Exception;
  11. use RuntimeException;
  12. use lithium\core\Libraries;
  13. use lithium\util\String;
  14. use lithium\util\Inflector;
  15. /**
  16. * The Library command is used to archive and extract Phar::GZ archives. Requires zlib extension.
  17. * In addition, communicate with the a given server to add plugins and extensions to the
  18. * current application. Push archived plugins to the server.
  19. *
  20. */
  21. class Library extends \lithium\console\Command {
  22. /**
  23. * Absolute path to config file.
  24. *
  25. * @var string
  26. */
  27. public $conf = null;
  28. /**
  29. * Path to where plugins will be installed. Relative to current working directory.
  30. *
  31. * @var string
  32. */
  33. public $path = null;
  34. /**
  35. * Server host to query for plugins.
  36. *
  37. * @var string
  38. */
  39. public $server = 'lab.lithify.me';
  40. /**
  41. * The port for the server.
  42. *
  43. * @var string
  44. */
  45. public $port = 80;
  46. /**
  47. * The username for the server authentication.
  48. *
  49. * @var string
  50. */
  51. public $username = '';
  52. /**
  53. * The password for corresponding username.
  54. *
  55. * @var string
  56. */
  57. public $password = '';
  58. /**
  59. * @see `force`
  60. * @var boolean
  61. */
  62. public $f = false;
  63. /**
  64. * Force operation to complete. Typically used for overwriting files.
  65. *
  66. * @var string
  67. */
  68. public $force = false;
  69. /**
  70. * Filter used for including files in archive.
  71. *
  72. * @var string
  73. */
  74. public $filter = '/\.(php|htaccess|jpg|png|gif|css|js|ico|json|ini)|(empty)$/';
  75. /**
  76. * Namespace used for newly extracted libraries.
  77. * Will default to the basename of the directory
  78. * the library is being extracted to.
  79. *
  80. * @var string
  81. */
  82. public $namespace = null;
  83. /**
  84. * When extracting a library, custom replacements
  85. * can be made on the extracted files that
  86. * are defined in this json file.
  87. *
  88. * @var string
  89. */
  90. public $replacementsFile = '_replacements.json';
  91. /**
  92. * The path to use for the `LITHIUM_LIBRARY_PATH`
  93. * in extracted templates. It defaults to the
  94. * current value of the `LITHIUM_LIBRARY_PATH`
  95. * constant. If `LITHIUM_LIBRARY_PATH` is not the same
  96. * as `dirname(LITHIUM_APP_PATH) . '/libraries'` then
  97. * the value of `LITHIUM_LIBRARY_PATH` will be hard-coded
  98. * to the `config/bootstrap/libraries.php` file in the
  99. * extracted library. If you want it to use a custom
  100. * value, then pass it to this option. For example,
  101. * if you keep your apps in the same directory as your
  102. * libraries, you could set it to `dirname(LITHIUM_APP_PATH)`
  103. *
  104. * @var string
  105. */
  106. public $lithiumLibraryPath = LITHIUM_LIBRARY_PATH;
  107. /**
  108. * Holds settings from conf file
  109. *
  110. * @var array
  111. */
  112. protected $_settings = array();
  113. /**
  114. * some classes
  115. *
  116. * @var array
  117. */
  118. protected $_classes = array(
  119. 'service' => 'lithium\net\http\Service',
  120. 'response' => 'lithium\console\Response'
  121. );
  122. /**
  123. * Auto configuration properties.
  124. *
  125. * @var array
  126. */
  127. protected $_autoConfig = array(
  128. 'classes' => 'merge', 'env', 'detectors' => 'merge', 'base', 'type', 'stream'
  129. );
  130. /**
  131. * Initialize _settings from `--conf`.
  132. *
  133. * Throws an exception if the command is initialized without a request object
  134. * which is needed by `_toPath()` in order to determine the current working directory.
  135. * This most often happens if the command is inspected using the `ReflectionClass`.
  136. *
  137. * @return void
  138. */
  139. protected function _init() {
  140. parent::_init();
  141. if ($this->server) {
  142. $this->_settings['servers'][$this->server] = true;
  143. }
  144. if (file_exists($this->conf)) {
  145. $this->_settings += (array) json_decode($this->conf, true);
  146. }
  147. $this->path = $this->_toPath($this->path ?: 'libraries');
  148. $this->force = $this->f ? $this->f : $this->force;
  149. }
  150. /**
  151. * Add configuration and write data in json format.
  152. *
  153. * @param string $key (server)
  154. * @param string $value value of key
  155. * @param boolean|string $options [optional]
  156. * @return mixed Returns all settings if `$key` and `$value` aren't set. The only option for
  157. * `$key` right now is 'server'. Returns the bytes written to the configuration file.
  158. */
  159. public function config($key = null, $value = null, $options = true) {
  160. if (empty($key) || empty($value)) {
  161. return $this->_settings;
  162. }
  163. switch ($key) {
  164. case 'server':
  165. $this->_settings['servers'][$value] = $options;
  166. break;
  167. }
  168. return file_put_contents($this->conf, json_encode($this->_settings));
  169. }
  170. /**
  171. * Extract an archive into a path. If one param exists, the app.phar.gz template will be used.
  172. * If both parameters exist, then the first will be the template archive and the second will be
  173. * the name of the extracted archive
  174. *
  175. * - `li3 library extract myapp` : uses command/create/template/app.phar.gz
  176. * - `li3 library extract another_archive myapp` : uses
  177. * command/create/template/another_archive.phar.gz
  178. * - `li3 library extract plugin li3_plugin` : uses command/create/template/plugin.phar.gz
  179. * - `li3 library extract /full/path/to/a.phar.gz myapp` : paths that begin with a '/'
  180. * can extract from archives outside of the default command/create/template/
  181. * location
  182. *
  183. * @param string $name if only param, command/create/template/app.phar.gz extracted to $name
  184. * otherwise, the template name or full path to extract `from` phar.gz.
  185. * @param string $result if exists $name is extracted to $result
  186. * @return boolean
  187. */
  188. public function extract($name = 'new', $result = null) {
  189. $from = 'app';
  190. $to = $name;
  191. if ($result) {
  192. $from = $name;
  193. $to = $result;
  194. }
  195. $to = $this->_toPath($to);
  196. if ($from[0] !== '/') {
  197. $from = Libraries::locate('command.create.template', $from, array(
  198. 'filter' => false, 'type' => 'file', 'suffix' => '.phar.gz'
  199. ));
  200. if (!$from || is_array($from)) {
  201. return false;
  202. }
  203. }
  204. if (file_exists($from)) {
  205. try {
  206. $archive = new Phar($from);
  207. } catch (Exception $e) {
  208. $this->error($e->getMessage());
  209. return false;
  210. }
  211. if ($archive->extractTo($to)) {
  212. $this->out(basename($to) . " created in " . dirname($to) . " from {$from}");
  213. if (empty($this->namespace)) {
  214. $this->namespace = Inflector::underscore(basename($to));
  215. }
  216. $replacements = $this->_findReplacements($to);
  217. return $this->_replaceAfterExtract($to, compact('namespace', 'replacements'));
  218. }
  219. }
  220. $this->error("Could not extract {$to} from {$from}");
  221. return false;
  222. }
  223. /**
  224. * Helper method for `console\command\Library::extract()` to gather
  225. * replacements to perform on the newly extracted files
  226. *
  227. * It looks for a json file specified by `$this->replacementsFile`
  228. * which defaults to _replacements.json.
  229. *
  230. * Running eval on a php file to get the `$replacements`
  231. * would be more flexible than using json, but definitely much more of a
  232. * security hole if the library is not trusted.
  233. *
  234. * @param string $base File path to the extracted library
  235. * @return array A multi-dimensional array. Keys on the top level
  236. * are filenames or glob-style paths. Those hold an array
  237. * with keys being the search param and values being the
  238. * replacement values
  239. */
  240. protected function _findReplacements($base = null) {
  241. $replacements = null;
  242. if (file_exists($base . '/' . $this->replacementsFile)) {
  243. $replacementsFilename = $base . '/' . $this->replacementsFile;
  244. $replacements = json_decode(file_get_contents($replacementsFilename), true);
  245. if ($replacements !== false) {
  246. unlink($base . '/' . $this->replacementsFile);
  247. }
  248. }
  249. return $replacements;
  250. }
  251. /**
  252. * Helper method for `console\command\Library::extract()` to perform after-extract string
  253. * replacements.
  254. *
  255. * In the current implementation, it only sets the correct `LITHIUM_LIBRARY_PATH` when the
  256. * app.phar.gz archive was extracted. If you get any errors, please make sure that the console
  257. * script has read and write permissions to the extracted directory.
  258. *
  259. * @param string $extracted contains the path to the extracted archive.
  260. * @param array $options Valid options are:
  261. * - `'replacements'`: an array of string replacements indexed by filename.
  262. * It's also possible to use glob-style wildcards in the filename such
  263. * as `*` or `*.php` or `resources/g11n/*`. If the filename starts
  264. * with `*`, then that filename pattern will be recursively found
  265. * in every sub-directory. Additionally, each replacement can
  266. * use `String::insert()` style strings that will be replaced
  267. * with the data in the `data` option.
  268. * - `'data'`: an array with data that will be used to replace
  269. * `String::insert`-style placeholders in the `replacements` option.
  270. * By default, this includes 'namespace' and 'library' which are
  271. * both set to the extracted library's namespace.
  272. * @return boolean
  273. */
  274. protected function _replaceAfterExtract($extracted, $options = array()) {
  275. $namespace = $this->namespace;
  276. $library = $namespace;
  277. $data = compact('namespace', 'library');
  278. $replacements = array();
  279. extract($options);
  280. if (empty($replacements)) {
  281. $replacements = array(
  282. 'config/bootstrap/libraries.php' => array(
  283. "Libraries::add('app'" => "Libraries::add('{:namespace}'"
  284. ),
  285. '*.php' => array(
  286. "namespace app\\" => "namespace {:namespace}\\"
  287. )
  288. );
  289. }
  290. if (dirname(LITHIUM_APP_PATH) . '/libraries' !== $this->lithiumLibraryPath) {
  291. $pathinfo = pathinfo($this->lithiumLibraryPath);
  292. if ($pathinfo['dirname'] !== '.') {
  293. $this->lithiumLibraryPath = "'" . $this->lithiumLibraryPath . "'";
  294. }
  295. $search = 'define(\'LITHIUM_LIBRARY_PATH\', ';
  296. $search .= 'dirname(LITHIUM_APP_PATH) . \'/libraries\');';
  297. $replace = 'define(\'LITHIUM_LIBRARY_PATH\', ';
  298. $replace .= $this->lithiumLibraryPath . ');';
  299. if (!isset($replacements['config/bootstrap/libraries.php'])) {
  300. $replacements['config/bootstrap/libraries.php'] = array();
  301. }
  302. $replacements['config/bootstrap/libraries.php'][$search] = $replace;
  303. }
  304. foreach ($replacements as $filename => $definitions) {
  305. foreach ($definitions as $search => $replace) {
  306. unset($definitions[$search]);
  307. $search = String::insert($search, $data);
  308. $replace = String::insert($replace, $data);
  309. $definitions[$search] = $replace;
  310. }
  311. $paths = $this->_wildcardPaths($filename, $extracted);
  312. foreach ($paths as $filepath) {
  313. if (file_exists($filepath)) {
  314. $content = file_get_contents($filepath);
  315. if ($content === '') {
  316. continue;
  317. }
  318. $content = str_replace(
  319. array_keys($definitions),
  320. array_values($definitions),
  321. $content
  322. );
  323. if (!file_put_contents($filepath, $content)) {
  324. $this->error("Could not replace content in {$filepath}");
  325. return false;
  326. }
  327. }
  328. }
  329. }
  330. return true;
  331. }
  332. /**
  333. * Utility function that will return an array of
  334. * file paths relative to the `$base` path that
  335. * are found using a glob-style asterisk wildcards
  336. * such as `*` or `*.php` or `resources/g11n/*`. If the path starts
  337. * with `*`, then that filename pattern will be recursively found
  338. * in every sub-directory.
  339. *
  340. * @param string $path
  341. * @param string $base Base directory to search for matching files
  342. * @return array
  343. */
  344. protected function _wildcardPaths($path, $base = '') {
  345. if (strpos($path, '*') === false) {
  346. return array($base . '/' . $path);
  347. }
  348. if ($path[0] === '*') {
  349. $paths = array();
  350. $dirs = array($base);
  351. while (!empty($dirs)) {
  352. $dir = array_shift($dirs);
  353. $paths = array_merge($paths, glob($dir . '/' . $path));
  354. $dirs = array_merge(
  355. $dirs,
  356. array_filter(glob($dir . '/*'), function($path) {
  357. return is_dir($path);
  358. })
  359. );
  360. }
  361. } else {
  362. $paths = array_filter(glob($base . '/' . $path), function($path) {
  363. $basename = basename($path);
  364. return $basename !== '.' && $basename !== '..';
  365. });
  366. }
  367. return $paths;
  368. }
  369. /**
  370. * Create the Phar::GZ archive from a given directory. If no params, the current working
  371. * directory is archived with the name of that directory. If one param, the current working
  372. * directory will be archive with the name provided. If both params, the first is the
  373. * name or path to the library to archive and the second is the name of the resulting archive
  374. *
  375. * - `li3 library archive my_archive` : archives current working directory to my_archive.phar.gz
  376. * - `li3 library archive myapp my_archive` : archives 'myapp' to 'my_archive.phar.gz'
  377. *
  378. * @param string $name if only param, the archive name for the current working directory
  379. * otherwise, The library name or path to the directory to compress.
  380. * @param string $result if exists, The name of the resulting archive
  381. * @return boolean
  382. */
  383. public function archive($name = null, $result = null) {
  384. if (ini_get('phar.readonly') === '1') {
  385. throw new RuntimeException('Set `phar.readonly` to `0` in `php.ini`.');
  386. }
  387. $from = $name;
  388. $to = $name;
  389. if ($result) {
  390. $from = $name;
  391. $to = $result;
  392. }
  393. $path = $this->_toPath($to);
  394. if (file_exists("{$path}.phar")) {
  395. if (!$this->force) {
  396. $this->error(basename($path) . ".phar already exists in " . dirname($path));
  397. return false;
  398. }
  399. Phar::unlinkArchive("{$path}.phar");
  400. }
  401. try {
  402. $archive = new Phar("{$path}.phar");
  403. } catch (Exception $e) {
  404. $this->error($e->getMessage());
  405. return false;
  406. }
  407. $result = null;
  408. $from = $this->_toPath($from);
  409. if (is_dir($from)) {
  410. $result = (boolean) $archive->buildFromDirectory($from, $this->filter);
  411. }
  412. if (file_exists("{$path}.phar.gz")) {
  413. if (!$this->force) {
  414. $this->error(basename($path) . ".phar.gz already exists in " . dirname($path));
  415. return false;
  416. }
  417. Phar::unlinkArchive("{$path}.phar.gz");
  418. }
  419. if ($result) {
  420. $archive->compress(Phar::GZ);
  421. $this->out(basename($path) . ".phar.gz created in " . dirname($path) . " from {$from}");
  422. return true;
  423. }
  424. $this->error("Could not create archive from {$from}");
  425. return false;
  426. }
  427. /**
  428. * List all the plugins and extensions available on the server.
  429. *
  430. * @param string $type plugins|extensions
  431. */
  432. public function find($type = 'plugins') {
  433. $results = array();
  434. foreach ($this->_settings['servers'] as $server => $enabled) {
  435. if (!$enabled) {
  436. continue;
  437. }
  438. $service = $this->_instance('service', array(
  439. 'host' => $server, 'port' => $this->port
  440. ));
  441. $results[$server] = json_decode($service->get("lab/{$type}.json"));
  442. if (empty($results[$server])) {
  443. $this->out("No {$type} at {$server}");
  444. continue;
  445. }
  446. foreach ((array) $results[$server] as $data) {
  447. $name = isset($data->class) ? $data->class : $data->name;
  448. $header = "{$server} > {$name}";
  449. $out = array(
  450. "{$data->summary}",
  451. "Version: {$data->version}",
  452. "Created: {$data->created}"
  453. );
  454. $this->header($header);
  455. $this->out(array_filter($out));
  456. }
  457. }
  458. }
  459. /**
  460. * Install plugins or extensions to the current application.
  461. * For plugins, the install commands specified in the formula is run.
  462. *
  463. * @param string $name name of plugin to add
  464. * @return boolean
  465. */
  466. public function install($name = null) {
  467. $results = array();
  468. foreach ($this->_settings['servers'] as $server => $enabled) {
  469. if (!$enabled) {
  470. continue;
  471. }
  472. $service = $this->_instance('service', array('host' => $server, 'port' => $this->port));
  473. if ($plugin = json_decode($service->get("lab/{$name}.json"))) {
  474. break;
  475. }
  476. }
  477. if (empty($plugin->sources)) {
  478. $this->error("{$name} not found.");
  479. return false;
  480. }
  481. $hasGit = function () {
  482. return (strpos(shell_exec('git --version'), 'git version') !== false);
  483. };
  484. foreach ((array) $plugin->sources as $source) {
  485. if (strpos($source, 'phar.gz') !== false && file_exists($source)) {
  486. $written = file_put_contents(
  487. "{$this->path}/{$plugin->name}.phar.gz", file_get_contents($source)
  488. );
  489. if (!$written) {
  490. $this->error("{$plugin->name}.phar.gz could not be saved");
  491. return false;
  492. }
  493. $this->out("{$plugin->name}.phar.gz saved to {$this->path}");
  494. try {
  495. $archive = new Phar("{$this->path}/{$plugin->name}.phar.gz");
  496. if ($archive->extractTo("{$this->path}/{$plugin->name}")) {
  497. $this->out("{$plugin->name} installed to {$this->path}/{$plugin->name}");
  498. $this->out("Remember to update the bootstrap.");
  499. return true;
  500. }
  501. } catch (Exception $e) {
  502. $this->error($e->getMessage());
  503. }
  504. }
  505. $url = parse_url($source);
  506. if (!empty($url['scheme']) && $url['scheme'] === 'git' && $hasGit()) {
  507. $cmd = "cd {$this->path} && git clone --quiet {$source} {$plugin->name}";
  508. $result = shell_exec($cmd);
  509. if (is_dir("{$this->path}/{$plugin->name}")) {
  510. $this->out("{$plugin->name} installed to {$this->path}/{$plugin->name}");
  511. $this->out("Remember to update your bootstrap.");
  512. return true;
  513. }
  514. }
  515. }
  516. $this->out("{$plugin->name} not installed.");
  517. return false;
  518. }
  519. /**
  520. * Create a formula for the given library name
  521. *
  522. * @param string $name the library name or full path to the plugin
  523. * @return boolean
  524. */
  525. public function formulate($name = null) {
  526. if (!$name) {
  527. $name = $this->in("please supply a name");
  528. }
  529. $result = false;
  530. $path = $this->_toPath($name);
  531. $name = basename($path);
  532. $formula = "{$path}/config/{$name}.json";
  533. $data = array();
  534. if (file_exists($formula)) {
  535. $data = json_decode(file_get_contents($formula), true);
  536. }
  537. if (empty($data['version'])) {
  538. $data['version'] = $this->in("please supply a version");
  539. }
  540. if (empty($data['summary'])) {
  541. $data['summary'] = $this->in("please supply a summary");
  542. }
  543. if (file_exists($path) && !file_exists($formula)) {
  544. $defaults = array(
  545. 'name' => $name, 'version' => '0.1',
  546. 'summary' => "a plugin called {$name}",
  547. 'maintainers' => array(array(
  548. 'name' => '', 'email' => '', 'website' => ''
  549. )),
  550. 'sources' => array("http://{$this->server}/lab/download/{$name}.phar.gz"),
  551. 'commands' => array(
  552. 'install' => array(), 'update' => array(), 'remove' => array()
  553. ),
  554. 'requires' => array()
  555. );
  556. $data += $defaults;
  557. if (!is_dir(dirname($formula)) && !mkdir(dirname($formula), 0755, true)) {
  558. $this->error("Formula for {$name} not created in {$path}");
  559. return false;
  560. }
  561. }
  562. if (is_dir(dirname($formula)) && file_put_contents($formula, json_encode($data))) {
  563. $this->out("Formula for {$name} created in {$path}.");
  564. return true;
  565. }
  566. $this->error("Formula for {$name} not created in {$path}");
  567. return false;
  568. }
  569. /**
  570. * Send a plugin archive to the server. The plugin must have a formula.
  571. *
  572. * @param string $name the library name or full path to the archive to send
  573. * @return void
  574. */
  575. public function push($name = null) {
  576. if (!$name) {
  577. $name = $this->in("please supply a name");
  578. }
  579. $path = $this->_toPath($name);
  580. $name = basename($name);
  581. $file = "{$path}.phar.gz";
  582. if (!file_exists("phar://{$file}/config/{$name}.json")) {
  583. $this->error(array(
  584. "The formula for {$name} is missing.", "Run li3 library formulate {$name}"
  585. ));
  586. return false;
  587. }
  588. $formula = json_decode(file_get_contents("phar://{$file}/config/{$name}.json"));
  589. $isValid = (
  590. !empty($formula->name) && !empty($formula->version)
  591. && !empty($formula->summary) && !empty($formula->sources)
  592. );
  593. if (!$isValid) {
  594. $this->error(array(
  595. "The formula for {$name} is not valid.", "Run li3 library formulate {$name}"
  596. ));
  597. return false;
  598. }
  599. if (file_exists($file)) {
  600. $service = $this->_instance('service', array(
  601. 'host' => $this->server, 'port' => $this->port,
  602. 'auth' => 'Basic', 'username' => $this->username, 'password' => $this->password
  603. ));
  604. $boundary = md5(date('r', time()));
  605. $headers = array("Content-Type: multipart/form-data; boundary={$boundary}");
  606. $name = basename($file);
  607. $data = join("\r\n", array(
  608. "--{$boundary}",
  609. "Content-Disposition: form-data; name=\"phar\"; filename=\"{$name}\"",
  610. "Content-Type: application/phar", "",
  611. base64_encode(file_get_contents($file)),
  612. "--{$boundary}--"
  613. ));
  614. $result = json_decode($service->post(
  615. '/lab/server/receive', $data, compact('headers')
  616. ));
  617. if ($service->last->response->status['code'] == 201) {
  618. $this->out(array(
  619. "{$result->name} added to {$this->server}.",
  620. "See http://{$this->server}/lab/plugins/view/{$result->id}"
  621. ));
  622. return $result;
  623. }
  624. if (!empty($result->error)) {
  625. $this->error($result->error);
  626. return false;
  627. }
  628. $this->error((array) $result);
  629. return false;
  630. }
  631. $this->error(array("{$file} does not exist.", "Run li3 library archive {$name}"));
  632. return false;
  633. }
  634. /**
  635. * Update installed plugins. For plugins, runs update commands specified in Formula.
  636. *
  637. * @todo implement
  638. * @return void
  639. */
  640. public function update() {
  641. $this->error('Please implement me');
  642. }
  643. /**
  644. * Take a name and return the path.
  645. *
  646. * If the name already appears to be a path, it is returned directly. Otherwise, the
  647. * `Library` class is used to find the associated path.
  648. *
  649. * @param string $name
  650. * @return string
  651. */
  652. protected function _toPath($name = null) {
  653. $pathinfo = pathinfo($name);
  654. if ($name && $pathinfo['dirname'] !== '.') {
  655. return $name;
  656. }
  657. $library = Libraries::get($name);
  658. if (!empty($library['path'])) {
  659. return $library['path'];
  660. }
  661. $path = $this->request->env('working');
  662. return (!empty($name)) ? "{$path}/{$name}" : $path;
  663. }
  664. }
  665. ?>