Perforce Chronicle 2012.2/486814
API Documentation

Ide_IndexController Class Reference

Simple back-end for the IDE editor. More...

List of all members.

Public Member Functions

 copyAction ()
 Support copying a file or directory.
 filesAction ()
 Support reading and writing of server files.
 indexAction ()
 Render the file editor (dijit).
 init ()
 Enforce permissions.
 packageAction ()
 Create a new module or theme.
 pathsAction ()
 Support listing directory contents.

Public Attributes

 $contexts

Protected Member Functions

 _getRootPath ()
 Get the root path above which no files will be read/written.
 _resolvePath ($path)
 Resolve given path to a location under the root path.

Protected Attributes

 $_hiddenFiles

Detailed Description

Simple back-end for the IDE editor.

Copyright:
2011-2012 Perforce Software. All rights reserved
License:
Please see LICENSE.txt in top-level folder of this distribution.
Version:
2012.2/486814

Member Function Documentation

Ide_IndexController::_getRootPath ( ) [protected]

Get the root path above which no files will be read/written.

Only files under the root path will be exposed via this controller.

Returns:
string the path files must be under to be exposed.
    {
        return realpath(SITES_PATH);
    }
Ide_IndexController::_resolvePath ( path) [protected]

Resolve given path to a location under the root path.

Parameters:
string$paththe relative path to resolve.
Returns:
string|bool the resolved path or false if the path cannot be resolved to a location under the root path.
    {
        $path = $this->_getRootPath() . '/' . trim($path, '/');
        $path = realpath($path);

        if (!$path) {
            return false;
        }

        $root = $this->_getRootPath();
        if (!$root || strpos($path, $root) !== 0) {
            return false;
        }

        return $path;
    }
Ide_IndexController::copyAction ( )

Support copying a file or directory.

    {
        $this->contextSwitch->initContext('json');

        $view    = $this->view;
        $request = $this->getRequest();

        // extract/normalize source and target details.
        $source  = $this->_resolvePath($request->getParam('source'));
        $target  = trim($request->getParam('target'), '/');
        $parent  = $this->_resolvePath(dirname($target));

        // verify:
        //  - request was posted
        //  - source exists
        //  - target does not exist
        //  - target parent folder exists
        $error = "Cannot copy from $source to $target. ";
        if (!$request->isPost()) {
            throw new Exception($message . "Request method must be POST.");
        }
        if (!$source) {
            throw new Exception($message . "Source path does not exist.");
        }
        if (!$parent) {
            throw new Exception($message . "Parent of target path does not exist.");
        }
        if ($this->_resolvePath($target)) {
            throw new Exception($message . "Target path already exists.");
        }

        P4Cms_FileUtility::copyRecursive($source, $parent . '/' . basename($target));
    }
Ide_IndexController::filesAction ( )

Support reading and writing of server files.

  • On get, reads file and presents file contents
  • On post, writes file and presents bytes written
  • On delete, removes file and presents 'true' or 'false' for success/failure.
    {
        $view     = $this->view;
        $request  = $this->getRequest();
        $file     = $this->_resolvePath($request->getParam('file'));
        $basename = basename($request->getParam('file'));

        // if user has modified a package file, clear package cache.
        if ($request->isDelete() || $request->isPost()) {
            if ($basename === 'theme.ini') {
                P4Cms_Theme::clearCache();
            }
            if ($basename === 'module.ini') {
                P4Cms_Module::clearCache();
            }
        }

        // delete request method implies unlink.
        if ($request->isDelete()) {
            // attempt to make file writable.
            if (!is_writable($file)) {
                @chmod($file, 0755);
            }

            // present true/false.
            $view->data = @unlink($file);
            return;
        }

        // post request method implies writing.
        if ($request->isPost()) {
            // if file did not resolve, create a new file if the path exists.
            if (!$file) {
                $path = $this->_resolvePath(dirname($request->getParam('file')));
                $file = $path . "/" . $basename;
                @touch($file);
            }

            // attempt to make file writable.
            if (!is_writable($file)) {
                @chmod($file, 0755);
            }

            // present bytes written.
            // if file content was uploaded, move the temp file into place.
            // otherwise, write contents of 'data' request param to the file.
            if (isset($_FILES['data']['tmp_name'])) {
                $result     = @move_uploaded_file($_FILES['data']['tmp_name'], $file);
                $view->data = $result ? $_FILES['data']['size'] : false;
            } else {
                $view->data = @file_put_contents($file, $request->getParam('data'));
            }

            return;
        }

        $view->file = $file;
    }
Ide_IndexController::indexAction ( )

Render the file editor (dijit).

    {
        $this->getHelper('layout')->setLayout('editor-layout');
        $this->view->headTitle()->set('IDE');

        // ace scripts are stored here instead of the .ini file so they are only loaded on the IDE page.
        $aceScripts = array(
            // main ace script
            "ace-uncompressed.js",

            // themes
            "theme-chrome.js", "theme-clouds.js", "theme-cobalt.js", "theme-crimson_editor.js",
            "theme-dawn.js", "theme-eclipse.js", "theme-idle_fingers.js", "theme-kr_theme.js",
            "theme-merbivore.js", "theme-merbivore_soft.js", "theme-mono_industrial.js",
            "theme-monokai.js", "theme-pastel_on_dark.js", "theme-solarized_dark.js",
            "theme-solarized_light.js", "theme-textmate.js", "theme-twilight.js", "theme-tomorrow.js",
            "theme-tomorrow_night.js", "theme-tomorrow_night_blue.js", "theme-tomorrow_night_bright.js",
            "theme-tomorrow_night_eighties.js", "theme-vibrant_ink.js",

            // syntax highlighting modes
            "mode-css.js", "mode-html.js", "mode-javascript.js", "mode-json.js", "mode-php.js",
            "mode-xml.js"
        );

        $module = P4Cms_Module::fetch('ide');
        foreach ($aceScripts as $script) {
            $this->view->headScript()->appendFile($module->getBaseUrl() . '/ace/' . $script);
        }
    }
Ide_IndexController::init ( )

Enforce permissions.

    {
        $this->getHelper('acl')->check('system', 'ide');
        $this->getHelper('layout')->disableLayout();
    }
Ide_IndexController::packageAction ( )

Create a new module or theme.

    {
        $this->contextSwitch->initContext('json');

        $view        = $this->view;
        $request     = $this->getRequest();
        $type        = $request->getParam('type');
        $label       = $request->getParam('name');
        $name        = strtolower(preg_replace('/[^a-z0-9]/i', '', $label));
        $namespace   = ucfirst($name);
        $description = $request->getParam('description');
        $tags        = $request->getParam('tags');
        $path        = $this->_getRootPath() . '/all/' . $type . 's/' . $name;

        // verify package type is valid and package does not already exist.
        $error = "Cannot create '$label' package. ";
        if ($type !== 'module' && $type !== 'theme') {
            throw new Exception($error . "Invalid package type specified.");
        }
        if (file_exists($path)) {
            throw new Exception($error . ucfirst($type) . " already exists.");
        }

        // copy the package template into place.
        P4Cms_FileUtility::copyRecursive(
            dirname(__DIR__) . '/templates/' . $type, $path
        );

        // provide 'package' macro for exclusive use by this action.
        // (it would not work as a general purpose macro).
        P4Cms_PubSub::subscribe('p4cms.macro.package',
            function($params, $body, $context) use ($label, $name, $namespace, $description, $tags)
            {
                $field = isset($params[0]) ? $params[0] : 'name';
                switch ($field) {
                    case 'label':
                        return $label;
                        break;
                    case 'name':
                        return $name;
                        break;
                    case 'namespace':
                        return $namespace;
                        break;
                    case 'description':
                        return $description;
                        break;
                    case 'tags':
                        return $tags;
                        break;
                    default:
                        return null;
                }
            }
        );

        // iterate over newly copied files and expand macros.
        $filter = new P4Cms_Filter_Macro;
        $files  = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator(
                $path,
                RecursiveDirectoryIterator::SKIP_DOTS
            ),
            RecursiveIteratorIterator::SELF_FIRST
        );
        foreach ($files as $file) {
            if (!$file->isDir()) {
                $contents = file_get_contents($file->getPathname());
                $contents = $filter->filter($contents);
                file_put_contents($file->getPathname(), $contents);
            }
        }

        // success!
        $view->data = true;
    }
Ide_IndexController::pathsAction ( )

Support listing directory contents.

Response format is suitable for consumption by a dijit.Tree using the ForestStoreModel and JsonRestStore.

    {
        $this->contextSwitch->initContext('json');

        $view       = $this->view;
        $request    = $this->getRequest();

        // extract path parameter.
        $path       = $request->getParam('path');
        $root       = $this->_getRootPath();
        $isRoot     = $path == 'root';

        // delete request method implies recursive unlink.
        if ($request->isDelete()) {
            // if path fails to resolve, it must not exist.
            $path = $this->_resolvePath($path);
            if (!$path) {
                throw new Exception(
                    "Cannot delete '$path'. Folder doesn't exist."
                );
            }

            // present true/false.
            $view->data = P4Cms_FileUtility::deleteRecursive($path);
            return;
        }

        // if a path was posted, attempt to create it.
        if ($request->getPost('path') && !$isRoot) {
            // strip leading/trailing slashes.
            $path = trim($path, '/');

            // if path resolves, it must exist already.
            if ($this->_resolvePath($path)) {
                throw new Exception(
                    "Cannot create '$path'. Path already exists."
                );
            }

            // verify parent folder resolves.
            $parent = $this->_resolvePath(dirname($path));
            if (!$parent) {
                throw new Exception(
                    "Cannot create '$path'. Containing folder doesn't exist."
                );
            }

            // attempt to make containing folder writable.
            if (!is_writable($parent)) {
                @chmod($parent, 0755);
            }

            // try to make it.
            $view->data = @mkdir($parent . '/' . basename($path), 0755);
            return;
        }

        // normalize path parameter.
        $path       = $isRoot ? $root : $this->_resolvePath($path);

        // collect entries in given path.
        $data       = array();
        $paths      = new DirectoryIterator($path);
        foreach ($paths as $entry) {
            if ($entry->isDot() || in_array($entry->getBasename(), $this->_hiddenFiles)) {
                continue;
            }

            $basename = $entry->getBasename();
            $pathname = str_replace($root . '/', '', $entry->getPathname());

            // if entry has children, only partially load it
            // (this uses dojo's JSON references (lazy-loading)
            if ($entry->isDir()) {
                $data[$basename] = array(
                    '$ref'      => $pathname,
                    'name'      => $basename,
                    'children'  => true
                );
            } else {
                $data[$basename] = array(
                    'id'        => $pathname,
                    'name'      => $basename,
                    'type'      => P4Cms_FileUtility::getMimeType($entry->getPathname())
                );
            }
        }

        // ensure orderly results.
        uksort($data, 'strnatcasecmp');
        $data = array_values($data);

        if (!$isRoot) {
            $path = $this->getRequest()->getParam('path');
            $data = array(
                'id'        => $path,
                'name'      => basename($path),
                'children'  => $data
            );
        }

        $view->data = $data;
    }

Member Data Documentation

Ide_IndexController::$_hiddenFiles [protected]
Initial value:
 array(
        '.DS_Store',
        '.placeholder'
    )
Ide_IndexController::$contexts
Initial value:
 array(
        'files'     => array('json'),
        'paths'     => array('json'),
        'copy'      => array('json'),
        'package'   => array('json')
    )

The documentation for this class was generated from the following file: