public function getSavestring(System\Entity $content = null) { $old_value = $content[$this['keyword']]; if ($old_value != $this->value) { if (!empty($old_value)) { $old_value = fx::path()->abs($old_value); if (file_exists($old_value) && is_file($old_value)) { fx::files()->rm($old_value); } } if (!empty($this->value)) { $c_val = fx::path()->abs($this->value); if (file_exists($c_val) && is_file($c_val)) { preg_match("~[^" . preg_quote(DIRECTORY_SEPARATOR) . ']+$~', $c_val, $fn); $path = fx::path()->http('@content_files/' . $content['site_id'] . '/' . $content['type'] . '/' . $this['keyword'] . '/' . $fn[0]); $try = 0; while (fx::path()->exists($path)) { $file_name = preg_replace("~(\\.[^\\.]+)\$~", "_" . $try . "\$1", $fn[0]); $try++; $path = fx::path()->http('@content_files/' . $content['type'] . '/' . $this['keyword'] . '/' . $file_name); } fx::files()->move($c_val, $path); } } } $res = isset($path) ? $path : $this->value; return $res; }
public function run($args = null) { if (!$args) { $args = isset($_SERVER['argv']) ? $_SERVER['argv'] : array(__FILE__); } elseif (is_scalar($args)) { $arg_string = $args; $args = array(\Floxim\Floxim\System\Fx::path()->fileName(__FILE__)); foreach (self::parseArgs($arg_string) as $arg) { $args[] = $arg; } } $this->scriptName = $args[0]; array_shift($args); // Define command if (isset($args[0])) { $name = $args[0]; array_shift($args); } else { $name = 'help'; } // Create command if (!($command = $this->createCommand($name))) { $command = $this->createCommand('help'); } return $command->run($args); }
public function route($url = null, $context = null) { $thumbs_path = fx::path()->http('@thumbs'); $thumbs_path_trimmed = fx::path()->removeBase($thumbs_path); if (substr($url, 0, strlen($thumbs_path_trimmed)) !== $thumbs_path_trimmed) { return null; } $dir = substr($url, strlen($thumbs_path_trimmed)); preg_match("~/([^/]+)(/.+\$)~", $dir, $parts); $config = $parts[1]; $source_path = $parts[2]; $source_abs = fx::path($source_path); if (!file_exists($source_abs)) { return null; } $target_dir = dirname(fx::path('@home/' . $url)); if (!file_exists($target_dir) || !is_dir($target_dir)) { return null; } $config = $config . '.async-false.output-true'; $config = \Floxim\Floxim\System\Thumb::readConfigFromPathString($config); fx::image($source_path, $config); fx::complete(); die; }
public function getSavestring(System\Entity $content = null) { $old_value = $content[$this['keyword']]; $old_path = FX_BASE_URL . $old_value; if ($old_path === $this->value) { return $this->value; } $res = ''; if (!empty($this->value)) { $c_val = fx::path()->abs($this->value); if (file_exists($c_val) && is_file($c_val)) { $file_name = fx::path()->fileName($c_val); $path = fx::path('@content_files/' . $content['site_id'] . '/' . $content['type'] . '/' . $this['keyword'] . '/' . $file_name); $path = fx::files()->getPutFilePath($path); fx::files()->move($c_val, $path); $res = fx::path()->removeBase(fx::path()->http($path)); } } if (!empty($old_value)) { $old_value = fx::path()->abs($old_value); if (file_exists($old_value) && is_file($old_value)) { fx::files()->rm($old_value); } } return $res; }
public static function addAdminAssets() { if (fx::isAdmin()) { $path = fx::path('@module/Floxim/Ui/Grid'); fx::page()->addJsFile($path . '/grid-builder.js'); fx::page()->addCssBundle(array($path . '/grid-builder.less')); } }
public static function checkValueIsFile($v) { if (empty($v)) { return false; } $files_path = fx::path('@files'); $path = fx::path(); return $path->isInside($v, $files_path) && $path->isFile($v); }
protected function getConfigSources() { $sources = array(); $c_name = $this->getControllerName(); $com_file = fx::path('@module/' . fx::getComponentPath($c_name) . '/cfg.php'); if (file_exists($com_file)) { $sources[] = $com_file; } return $sources; }
public function dropDictFiles($dict) { $files = glob(fx::path('@files/php_dictionaries/*.' . $dict . '.php')); if (!$files) { return; } foreach ($files as $file) { unlink($file); } }
public function route($url = null, $context = null) { if (!fx::isAdmin()) { return null; } if (!preg_match("~^/\\~ib/(\\d+|fake(?:\\-\\d+)?)@(\\d+)~", $url, $ib_info)) { return null; } $c_url = fx::input()->fetchGetPost('_ajax_base_url'); if ($c_url) { $_SERVER['REQUEST_URI'] = $c_url; $path = fx::router()->getPath(fx::path()->removeBase($c_url)); if ($path) { fx::env('page', $path->last()); } else { fx::env('page', fx::router('error')->getErrorPage()); } $c_url = parse_url($c_url); if (isset($c_url['query'])) { parse_str($c_url['query'], $_GET); } } $ib_id = $ib_info[1]; $page_id = $ib_info[2]; if (!fx::env('page') && $page_id) { $page = fx::data('floxim.main.content', $page_id); fx::env('page', $page); } fx::env('ajax', true); $page_infoblocks = fx::router('front')->getPageInfoblocks($page_id, fx::env('layout')); fx::page()->setInfoblocks($page_infoblocks); // import layout template to recreate real env fx::router('front')->importLayoutTemplate(); // front end can try to reload the layout which is out of date // when updating from "layout settings" panel $infoblock = fx::data('infoblock', $ib_id); if (!$infoblock && isset($_POST['infoblock_is_layout']) || $infoblock->isLayout()) { //$infoblock = $layout_infoblock; $infoblock = fx::router('front')->getLayoutInfoblock(fx::env('page')); } fx::http()->status('200'); $infoblock_overs = null; if (fx::isAdmin() && isset($_POST['override_infoblock'])) { $infoblock_overs = fx::input('post', 'override_infoblock'); if (is_string($infoblock_overs)) { parse_str($infoblock_overs, $infoblock_overs); $infoblock_overs = fx::input()->prepareSuperglobal($infoblock_overs); } $infoblock->override($infoblock_overs); } $infoblock->overrideParam('ajax_mode', true); $res = $infoblock->render(); return $res; }
protected function scaffold(\Floxim\Floxim\Component\Widget\Entity $com, $overwrite = false) { $source_path = fx::path('@floxim/Console/protected/widget'); $component_path = $com->getPath(); $file_list = $this->buildFileList($source_path, $component_path); foreach ($file_list as &$file_data) { $file_data['callback_content'] = function ($content) use($com) { return Widget::replacePlaceholder($content, $com); }; } $this->copyFiles($file_list); }
public function show($input) { $this->response->breadcrumb->addItem(fx::alang('Console'), '#admin.console.show'); $this->response->submenu->setMenu('console'); $fields = array('console_text' => array('name' => 'console_text', 'type' => 'text', 'code' => 'htmlmixed', 'value' => isset($input['console_text']) ? $input['console_text'] : '<?php' . "\n")); $fields[] = $this->ui->hidden('entity', 'console'); $fields[] = $this->ui->hidden('action', 'execute'); $this->response->addFormButton(array('label' => fx::alang('Execute') . ' (Ctrl+Enter)', 'is_submit' => false, 'class' => 'execute')); $this->response->addFields($fields); fx::page()->addJsFile(fx::path('@floxim') . '/Admin/js/console.js'); return array('show_result' => 1); }
protected function getConfigSources() { $sources = array(); $sources[] = fx::path('@module/' . fx::getComponentPath('floxim.main.content') . '/cfg.php'); $com = $this->getComponent(); $chain = $com->getChain(); foreach ($chain as $com) { $com_file = fx::path('@module/' . fx::getComponentPath($com['keyword']) . '/cfg.php'); if (file_exists($com_file)) { $sources[] = $com_file; } } return $sources; }
public function getPath($url, $site_id = null) { $url = fx::path()->removeBase($url); $url = preg_replace("~\\#.*\$~", '', $url); if (is_null($site_id)) { $site_id = fx::env('site_id'); } // @todo check if url contains another host name $url = preg_replace("~^https?://.+?/~", '/', $url); foreach ($this->routers as $rk => $r) { $result = $r['router']->getPath($url, $site_id); if ($result) { return $result; } } }
public function route($url = null, $context = null) { $adm_path = '/' . fx::config('path.admin_dir_name') . '/'; if (trim($url, '/') === trim($adm_path, '/') && $url !== $adm_path) { fx::http()->redirect(fx::config('paht.admin'), 301); } if ($url !== $adm_path) { return null; } $input = fx::input()->makeInput(); $entity = fx::input()->fetchPost('entity'); $action = fx::input()->fetchPost('action'); if (!$entity || !$action) { fx::page()->setBaseUrl(FX_BASE_URL . '/' . trim($adm_path, '/')); return new Controller\Admin(); } $base_url = fx::input()->fetchPost('_base_url'); if ($base_url) { $base_path = fx::router()->getPath(fx::path()->removeBase($base_url)); if ($base_path) { fx::env('page', $base_path->last()); } } fx::env('ajax', true); $posting = fx::input()->fetchPost('posting'); if (!preg_match("~^module_~", $entity) || fx::input()->fetchPost('fx_admin')) { $entity = 'admin_' . $entity; } if ($posting && $posting !== 'false') { $action .= "_save"; } $path = explode('_', $entity, 2); if ($path[0] == 'admin') { $classname = 'Floxim\\Floxim\\Admin\\Controller\\' . fx::util()->underscoreToCamel($path[1]); } else { // todo: psr0 what? } try { $controller = new $classname($input, $action); } catch (\Exception $e) { die("Error! Entity: " . htmlspecialchars($entity)); } //header("Content-type: application/json; charset=utf-8"); return $controller; }
protected function handleThumbs() { fx::listen('unlink', function ($e) { $f = $e['file']; if (fx::files()->isMetaFile($f)) { return; } if (fx::path()->isInside($f, fx::path('@thumbs'))) { return; } if (!fx::path()->isInside($f, fx::path('@content_files'))) { return; } $thumbs = \Floxim\Floxim\System\Thumb::findThumbs($f); foreach ($thumbs as $thumb) { fx::files()->rm($thumb); } }); }
public function doScaffold($name, $overwrite = false) { $name_parts = explode('.', $name); if (count($name_parts) != 2) { $this->usageError('Name need format "vendor.name"'); } $this->module_vendor = fx::util()->underscoreToCamel($name_parts[0]); $this->module_name = fx::util()->underscoreToCamel($name_parts[1]); /** * Check for exists */ $module_path = fx::path('@root') . "module/{$this->module_vendor}/{$this->module_name}/"; if (file_exists($module_path)) { if (!$overwrite) { $this->usageError('Module already exists'); } } else { /** * Create dir */ if (@mkdir($module_path, 0777, true)) { echo "Create dir {$module_path}" . "\n"; } else { $this->usageError('Can\'t create module dir - ' . $module_path); } } $source_path = fx::path('@floxim') . '/Console/protected/module/'; /** * Build file list */ $file_list = $this->buildFileList($source_path, $module_path); foreach ($file_list as $file_name => $file_data) { $file_list[$file_name]['callback_content'] = array($this, 'replacePlaceholder'); } /** * Copy files */ $this->copyFiles($file_list); echo "\nYour module has been created successfully under {$module_path}.\n"; }
public function getLogoutUrl() { return fx::path()->http('@home/~ajax/floxim.user.user:logout/'); }
protected function afterUpdate() { parent::afterUpdate(); // modified image fields $image_fields = $this->getFields()->find('keyword', $this->modified)->find('type', array(Field\Entity::FIELD_IMAGE, Field\Entity::FIELD_FILE)); foreach ($image_fields as $img_field) { $old_value = $this->modified_data[$img_field['keyword']]; if (fx::path()->isFile($old_value)) { fx::files()->rm($old_value); } } }
public function getTemplateInfo($full_id) { $tpl = fx::template($full_id); if (!$tpl) { return; } $info = $tpl->getInfo(); if (!isset($info['file']) || !isset($info['offset'])) { return; } $res = array(); $source = file_get_contents(fx::path()->abs($info['file'])); $res['file'] = $info['file']; $res['hash'] = md5($source); $res['full'] = $source; $offset = explode(',', $info['offset']); $length = $offset[1] - $offset[0]; $res['start'] = $offset[0]; $res['length'] = $length; $source_part = $res['source'] = mb_substr($source, $offset[0], $length); $first_part = mb_substr($source, 0, $res['start']); $res['first_line'] = count(explode("\n", $first_part)); $space_tale = ''; if (preg_match("~[ \t]+\$~", $first_part, $space_tale)) { $space_tale = $space_tale[0]; $tale_length = strlen($space_tale); $lines = explode("\n", $source_part); foreach ($lines as &$l) { if (substr($l, 0, $tale_length) === $space_tale) { $l = substr($l, $tale_length); } } $source_part = join("\n", $lines); } $res['common_spaces'] = $space_tale; $res['source'] = $source_part; return $res; }
public static function addAdminFiles() { $path_floxim = fx::path('@floxim'); $lang = fx::config('lang.admin'); $js_files = array(FX_JQUERY_PATH, $path_floxim . '/lib/js/jquery.bem.js', $path_floxim . '/Admin/js/fxj.js', $path_floxim . '/Admin/js/fx.js', $path_floxim . '/Admin/js/js-dictionary-' . $lang . '.js', FX_JQUERY_UI_PATH, $lang === 'en' ? null : $path_floxim . '/lib/js/jquery.datepicker.' . $lang . '.js', $path_floxim . '/lib/js/jquery.ba-hashchange.min.js', $path_floxim . '/lib/js/jquery.json-2.3.js', $path_floxim . '/lib/js/ajaxfileupload.js', $path_floxim . '/Admin/js-templates/jstx.js', 'http://' . getenv("HTTP_HOST") . fx::path()->http($path_floxim) . '/Admin/js-templates/compile.php', $path_floxim . '/Admin/js/lib.js', $path_floxim . '/Admin/js/sort.js', $path_floxim . '/Admin/js/front.js', $path_floxim . '/Admin/js/adder.js', $path_floxim . '/Admin/js/buttons.js', $path_floxim . '/Admin/js/form.js', $path_floxim . '/Admin/js/debug.js', $path_floxim . '/Admin/js/livesearch.js', $path_floxim . '/Admin/js/fields.js', $path_floxim . '/Admin/js/edit-in-place.js', $path_floxim . '/Admin/js/panel.js', $path_floxim . '/Admin/js/popup.js', $path_floxim . '/Admin/js/admin.js', $path_floxim . '/Admin/js/nav.js', $path_floxim . '/lib/editors/redactor/redactor.patched.js', $lang === 'en' ? null : $path_floxim . '/lib/editors/redactor/langs/' . $lang . '.js', $path_floxim . '/lib/editors/redactor/fontcolor.js', $path_floxim . '/lib/codemirror/codemirror.all.min.js', $path_floxim . '/lib/js/jquery.form.js', $path_floxim . '/lib/js/jquery.cookie.js', $path_floxim . '/lib/js/jquery.ba-resize.min.js', $path_floxim . '/lib/js/jquery.scrollTo.js', $path_floxim . '/Admin/js/map.js', $path_floxim . '/Admin/js/node-panel.js', $path_floxim . '/Admin/js/infoblock.js'); $page = fx::page(); $page->addJsBundle($js_files, array('name' => 'fx_admin')); $page->addCssFile('https://fonts.googleapis.com/css?family=Roboto:400,500,400italic,500italic,700,700italic&subset=latin,cyrillic'); // todo: need fix path for css - now used server path $page->addCssBundle(array($path_floxim . '/lib/editors/redactor/redactor.css')); $page->addCssBundle(array($path_floxim . '/Admin/style/mixins.less', $path_floxim . '/Admin/style/main.less', $path_floxim . '/Admin/style/backoffice.less', $path_floxim . '/Admin/style/forms.less', $path_floxim . '/Admin/style/front.less', $path_floxim . '/Admin/style/livesearch.less', $path_floxim . '/Admin/style/debug.less', $path_floxim . '/lib/codemirror/codemirror.css'), array('name' => 'admin_less')); }
public function getTargetPath() { if (!$this->target_dir) { $this->target_dir = fx::config('templates.cache_dir'); } if (!$this->target_file) { $this->target_file = $this->getTemplateName() . '.php'; } /** * Calc prefix hash by sources files */ if (!$this->target_hash) { $this->recalcTargetHash(); } $res = $this->target_dir . '/'; $res .= preg_replace("~\\.php\$~", '.' . $this->target_hash . '.php', $this->target_file); $res = fx::path($res); return $res; }
protected function getTemplateProps(Token $token) { $tpl_props = array(); $com_name = $this->template_set_name; $tpl_id = $token->getProp('id'); if (preg_match("~#(.+)\$~", $tpl_id, $tpl_tags)) { $tpl_id = preg_replace("~#.+\$~", '', $tpl_id); $tags = preg_split("~\\,\\s*~", $tpl_tags[1]); if (count($tags) > 0) { $tpl_props['tags'] = $tags; } } if (preg_match("~(^.+?)\\:(.+)~", $tpl_id, $id_parts)) { $external_com_name = $id_parts[1]; $own_name = $id_parts[2]; if ($external_com_name == $com_name) { $tpl_id = $own_name; } else { $tpl_props['overrides'] = $tpl_id; $tpl_id = str_replace(".", '_', $external_com_name) . '__' . $own_name; } } $tpl_props['id'] = $tpl_id; $tpl_props['file'] = fx::path()->http($this->current_source_file); $tpl_props['is_imported'] = $this->current_source_is_imported; $i_level = $this->current_source_import_level; $tpl_props['import_level'] = $i_level; if ($this->current_source_is_imported) { if (!isset($tpl_props['tags']) || !$tpl_props['tags']) { $tpl_props['tags'] = array(); } $tpl_props['tags'][] = 'imported' . ($i_level > 0 ? $i_level : ''); } if ($offset = $token->getProp('offset')) { $tpl_props['offset'] = $offset; } if ($size = $token->getProp('size')) { $tpl_props['size'] = $size; } if ($suit = $token->getProp('suit')) { $tpl_props['suit'] = $suit; } if ($area_id = $token->getProp('area')) { $tpl_props['area'] = $area_id; } if (!($name = $token->getProp('name'))) { $name = $token->getProp('id'); } $tpl_props['full_id'] = $com_name . ':' . $tpl_props['id']; $of = $token->getProp('of'); // todo: psr0 need fix $of_map = array('menu' => 'section:list', 'wrapper' => 'floxim.layout.wrapper:show', 'blockset' => 'blockset:show', 'grid' => 'grid:show', 'block' => 'block:show'); $of_priority = array(); if ($of and $of != 'false') { // $of is array when copied to {preset} from the extended template if (!is_array($of)) { $of_parts = explode(',', $of); array_walk($of_parts, function (&$v) { $v = trim($v); }); foreach ($of_parts as $key => $value) { $value_parts = explode('#', $value); if (count($value_parts) === 1) { $value_prior = 1; } else { $value = $value_parts[0]; $value_prior = (int) $value_parts[1]; } if (isset($of_map[$value])) { $value = $of_map[$value]; } $c_of = explode(":", $value); // no component, e.g. fx:of="list" if (count($c_of) === 1) { $of_parts[$key] = fx::getComponentFullName($com_name) . ':' . $value; } else { $of_parts[$key] = fx::getComponentFullName($c_of[0]) . ':' . $c_of[1]; } $of_priority[$of_parts[$key]] = $value_prior; } $of = join(',', $of_parts); } else { $of_priority = $of; } } else { $of = array(); } $tpl_props += array('name' => $name, 'of' => $of_priority, 'is_preset_of' => $token->getProp('is_preset_of'), 'replace_original' => $token->getProp('replace_original'), 'is_abstract' => $token->getProp('is_abstract'), '_token' => $token); return $tpl_props; }
/** * Show form to authorize user on all sites */ public function doCrossSiteAuthForm() { if (!preg_match("~/\\~ajax~", $_SERVER['REQUEST_URI'])) { return false; } if (!fx::user()->isAdmin()) { fx::http()->redirect('@home'); } $sites = fx::data('site')->all(); $hosts = array(); foreach ($sites as $site) { foreach ($site->getAllHosts() as $host) { if ($host === fx::env('host')) { continue; } $hosts[] = $host; } } fx::env('ajax', false); $target_location = fx::input()->fetchCookie('fx_target_location'); // unset cookie fx::input()->setCookie('fx_target_location', '', 1); if (!$target_location) { $target_location = '/'; } if (count($hosts) === 0) { fx::http()->redirect($target_location); } return array('hosts' => $hosts, 'auth_url' => fx::path()->http('@home/~ajax/floxim.user.user:cross_site_auth'), 'target_location' => $target_location, 'session_key' => fx::data('session')->load()->get('session_key')); }
public function getPath() { return fx::path('@module/' . fx::getComponentPath($this['keyword'])); }
public function getIcon($what_for) { $theme = fx::env('theme_template'); if (!$theme) { return; } $dirs = $theme->getTemplateSourceDirs(); if (!$dirs) { return; } $file = $what_for . '.png'; foreach ($dirs as $dir) { $c_file = $dir . '/icons/' . $file; if (file_exists(fx::path($c_file))) { return $c_file; } } }
public static function nameToPath($name) { $ns = fx::getComponentNamespace($name); $ns = explode("\\", trim($ns, "\\")); if ($ns[0] === 'Theme') { $ns[0] = 'theme'; } else { array_unshift($ns, 'module'); } return fx::path()->abs('/' . join("/", $ns)); }
public static function addAdminAssets() { $path = fx::path('@module/Floxim/Ui/Box'); fx::page()->addJsFile($path . '/box-builder.js'); fx::page()->addCssBundle(array($path . '/box-builder.less')); }
public function source($layout) { $template = fx::template('theme.' . $layout['keyword']); $vars = $template->getTemplateVariants(); $files = array(); foreach ($vars as $var) { $files[preg_replace("~^.+/~", '', $var['file'])] = $var['file']; } foreach ($files as $file => $path) { $tab_code = md5($file); //preg_replace("~\.~", '_', $file); $tab_name = fx::path()->fileName($file); $source = file_get_contents($path); $this->response->addTab($tab_code, $tab_name); $this->response->addFields(array(array('type' => 'text', 'code' => 'htmlmixed', 'name' => 'source_' . $tab_code, 'value' => $source)), $tab_code); } $fields = array($this->ui->hidden('entity', 'layout'), $this->ui->hidden('action', 'source')); $this->response->submenu->setMenu('layout'); $this->response->addFormButton('save'); return array('fields' => $fields, 'form_button' => array('save')); }
public function install() { if ($this['status'] != 'ready') { return false; } if (!$this['url']) { return false; } $dir = fx::path('@files/patches/' . $this['from'] . '-' . $this['to']); if (!file_exists($dir)) { $saved = fx::files()->saveFile($this['url'], 'patches/', $this['from'] . '-' . $this['to'] . '.zip'); fx::files()->unzip($saved['fullpath'], 'patches/' . $this['from'] . '-' . $this['to']); unlink($saved['fullpath']); } if (!file_exists($dir) || !is_dir($dir)) { return false; } /** * Load patch info */ $info_file = @json_decode(file_get_contents($dir . '/_patch_generator/patch.json'), true); if (!$info_file) { return false; } /** * Load hooks list */ $hook_objects = array(); if (isset($info_file['hooks'])) { foreach ($info_file['hooks'] as $hook_file) { require_once $dir . '/' . $hook_file; $hook_info = pathinfo($hook_file); if (class_exists($hook_info['filename'])) { $hook_objects[] = new $hook_info['filename'](); } } } /** * Run before hooks */ foreach ($hook_objects as $hook) { if (method_exists($hook, 'before')) { call_user_func(array($hook, 'before')); } } /** * Remove files */ if (isset($info_file['files']['del'])) { $this->removeFiles($info_file['files']['del']); } /** * Copy files */ if (file_exists($dir)) { $this->updateFiles($dir, $dir); } /** * Run migrations */ $migration_objects = array(); if (isset($info_file['migrations'])) { foreach ($info_file['migrations'] as $migration_file) { require_once $dir . '/' . $migration_file; $migration_info = pathinfo($migration_file); if (class_exists($migration_info['filename'])) { $migration_objects[] = new $migration_info['filename'](); } } } foreach ($migration_objects as $migration) { if (method_exists($migration, 'exec_up')) { call_user_func(array($migration, 'exec_up')); } } /** * Run after hooks */ foreach ($hook_objects as $hook) { if (method_exists($hook, 'after')) { call_user_func(array($hook, 'after')); } } $this->updateVersionNumber($this['to']); $this['status'] = 'installed'; $this->save(); $next_patch = fx::data('patch')->where('from', $this['to'])->one(); if ($next_patch) { $next_patch->set('status', 'ready')->save(); } return true; }
protected function afterDelete() { parent::afterDelete(); $path = fx::path()->abs($this->getPath()); fx::files()->rm($path); }