public function render() { $viewer = $this->getViewer(); if (!$viewer->isLoggedIn()) { return null; } $instructions_id = 'phabricator-global-drag-and-drop-upload-instructions'; require_celerity_resource('global-drag-and-drop-css'); $hint_text = $this->getHintText(); if (!strlen($hint_text)) { $hint_text = "⇪ " . pht('Drop Files to Upload'); } // Use the configured default view policy. Drag and drop uploads use // a more restrictive view policy if we don't specify a policy explicitly, // as the more restrictive policy is correct for most drop targets (like // Pholio uploads and Remarkup text areas). $view_policy = $this->getViewPolicy(); if ($view_policy === null) { $view_policy = PhabricatorFile::initializeNewFile()->getViewPolicy(); } $submit_uri = $this->getSubmitURI(); $done_uri = '/file/query/authored/'; Javelin::initBehavior('global-drag-and-drop', array('ifSupported' => $this->showIfSupportedID, 'instructions' => $instructions_id, 'uploadURI' => '/file/dropupload/', 'submitURI' => $submit_uri, 'browseURI' => $done_uri, 'viewPolicy' => $view_policy, 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold())); return phutil_tag('div', array('id' => $instructions_id, 'class' => 'phabricator-global-upload-instructions', 'style' => 'display: none;'), $hint_text); }
protected final function executeQuery() { $future = $this->newQueryFuture(); $drequest = $this->getRequest(); $name = basename($drequest->getPath()); $ttl = PhabricatorTime::getNow() + phutil_units('48 hours in seconds'); try { $threshold = PhabricatorFileStorageEngine::getChunkThreshold(); $future->setReadBufferSize($threshold); $source = id(new PhabricatorExecFutureFileUploadSource())->setName($name)->setTTL($ttl)->setViewPolicy(PhabricatorPolicies::POLICY_NOONE)->setExecFuture($future); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $file = $source->uploadFile(); unset($unguarded); } catch (CommandException $ex) { if (!$future->getWasKilledByTimeout()) { throw $ex; } $this->didHitTimeLimit = true; $file = null; } $byte_limit = $this->getByteLimit(); if ($byte_limit && $file->getByteSize() > $byte_limit) { $this->didHitByteLimit = true; $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); id(new PhabricatorDestructionEngine())->destroyObject($file); unset($unguarded); $file = null; } return $file; }
protected function execute(ConduitAPIRequest $request) { $viewer = $request->getUser(); $hash = $request->getValue('contentHash'); $name = $request->getValue('name'); $view_policy = $request->getValue('viewPolicy'); $length = $request->getValue('contentLength'); $properties = array('name' => $name, 'authorPHID' => $viewer->getPHID(), 'viewPolicy' => $view_policy, 'isExplicitUpload' => true); $ttl = $request->getValue('deleteAfterEpoch'); if ($ttl) { $properties['ttl'] = $ttl; } $file = null; if ($hash) { $file = PhabricatorFile::newFileFromContentHash($hash, $properties); } if ($hash && !$file) { $chunked_hash = PhabricatorChunkedFileStorageEngine::getChunkedHash($viewer, $hash); $file = id(new PhabricatorFileQuery())->setViewer($viewer)->withContentHashes(array($chunked_hash))->executeOne(); } if (strlen($name) && !$hash && !$file) { if ($length > PhabricatorFileStorageEngine::getChunkThreshold()) { // If we don't have a hash, but this file is large enough to store in // chunks and thus may be resumable, try to find a partially uploaded // file by the same author with the same name and same length. This // allows us to resume uploads in Javascript where we can't efficiently // compute file hashes. $file = id(new PhabricatorFileQuery())->setViewer($viewer)->withAuthorPHIDs(array($viewer->getPHID()))->withNames(array($name))->withLengthBetween($length, $length)->withIsPartial(true)->setLimit(1)->executeOne(); } } if ($file) { return array('upload' => (bool) $file->getIsPartial(), 'filePHID' => $file->getPHID()); } // If there are any non-chunk engines which this file can fit into, // just tell the client to upload the file. $engines = PhabricatorFileStorageEngine::loadStorageEngines($length); if ($engines) { return array('upload' => true, 'filePHID' => null); } // Otherwise, this is a large file and we want to perform a chunked // upload if we have a chunk engine available. $chunk_engines = PhabricatorFileStorageEngine::loadWritableChunkEngines(); if ($chunk_engines) { $chunk_properties = $properties; if ($hash) { $chunk_properties += array('chunkedHash' => $chunked_hash); } $chunk_engine = head($chunk_engines); $file = $chunk_engine->allocateChunks($length, $chunk_properties); return array('upload' => true, 'filePHID' => $file->getPHID()); } // None of the storage engines can accept this file. if (PhabricatorFileStorageEngine::loadWritableEngines()) { $error = pht('Unable to upload file: this file is too large for any ' . 'configured storage engine.'); } else { $error = pht('Unable to upload file: the server is not configured with any ' . 'writable storage engines.'); } return array('upload' => false, 'filePHID' => null, 'error' => $error); }
public function execute(PhutilArgumentParser $args) { $iterator = $this->buildIterator($args); if (!$iterator) { throw new PhutilArgumentUsageException(pht('Either specify a list of files to encode, or use --all to ' . 'encode all files.')); } $force = (bool) $args->getArg('force'); $format_list = PhabricatorFileStorageFormat::getAllFormats(); $format_list = array_keys($format_list); $format_list = implode(', ', $format_list); $format_key = $args->getArg('as'); if (!strlen($format_key)) { throw new PhutilArgumentUsageException(pht('Use --as <format> to select a target encoding format. Available ' . 'formats are: %s.', $format_list)); } $format = PhabricatorFileStorageFormat::getFormat($format_key); if (!$format) { throw new PhutilArgumentUsageException(pht('Storage format "%s" is not valid. Available formats are: %s.', $format_key, $format_list)); } $key_name = $args->getArg('key'); if (strlen($key_name)) { $format->selectMasterKey($key_name); } $engines = PhabricatorFileStorageEngine::loadAllEngines(); $failed = array(); foreach ($iterator as $file) { $monogram = $file->getMonogram(); $engine_key = $file->getStorageEngine(); $engine = idx($engines, $engine_key); if (!$engine) { echo tsprintf("%s\n", pht('%s: Uses unknown storage engine "%s".', $monogram, $engine_key)); $failed[] = $file; continue; } if ($engine->isChunkEngine()) { echo tsprintf("%s\n", pht('%s: Stored as chunks, no data to encode directly.', $monogram)); continue; } if ($file->getStorageFormat() == $format_key && !$force) { echo tsprintf("%s\n", pht('%s: Already encoded in target format.', $monogram)); continue; } echo tsprintf("%s\n", pht('%s: Changing encoding from "%s" to "%s".', $monogram, $file->getStorageFormat(), $format_key)); try { $file->migrateToStorageFormat($format); echo tsprintf("%s\n", pht('Done.')); } catch (Exception $ex) { echo tsprintf("%B\n", pht('Failed! %s', (string) $ex)); $failed[] = $file; } } if ($failed) { $monograms = mpull($failed, 'getMonogram'); echo tsprintf("%s\n", pht('Failures: %s.', implode(', ', $monograms))); return 1; } return 0; }
public function execute(PhutilArgumentParser $args) { $iterator = $this->buildIterator($args); if (!$iterator) { throw new PhutilArgumentUsageException(pht('Either specify a list of files to cycle, or use --all to cycle ' . 'all files.')); } $format_map = PhabricatorFileStorageFormat::getAllFormats(); $engines = PhabricatorFileStorageEngine::loadAllEngines(); $key_name = $args->getArg('key'); $failed = array(); foreach ($iterator as $file) { $monogram = $file->getMonogram(); $engine_key = $file->getStorageEngine(); $engine = idx($engines, $engine_key); if (!$engine) { echo tsprintf("%s\n", pht('%s: Uses unknown storage engine "%s".', $monogram, $engine_key)); $failed[] = $file; continue; } if ($engine->isChunkEngine()) { echo tsprintf("%s\n", pht('%s: Stored as chunks, declining to cycle directly.', $monogram)); continue; } $format_key = $file->getStorageFormat(); if (empty($format_map[$format_key])) { echo tsprintf("%s\n", pht('%s: Uses unknown storage format "%s".', $monogram, $format_key)); $failed[] = $file; continue; } $format = clone $format_map[$format_key]; $format->setFile($file); if (!$format->canCycleMasterKey()) { echo tsprintf("%s\n", pht('%s: Storage format ("%s") does not support key cycling.', $monogram, $format->getStorageFormatName())); continue; } echo tsprintf("%s\n", pht('%s: Cycling master key.', $monogram)); try { if ($key_name) { $format->selectMasterKey($key_name); } $file->cycleMasterStorageKey($format); echo tsprintf("%s\n", pht('Done.')); } catch (Exception $ex) { echo tsprintf("%B\n", pht('Failed! %s', (string) $ex)); $failed[] = $file; } } if ($failed) { $monograms = mpull($failed, 'getMonogram'); echo tsprintf("%s\n", pht('Failures: %s.', implode(', ', $monograms))); return 1; } return 0; }
/** * @phutil-external-symbol class PhabricatorStartup */ protected function executeChecks() { $engines = PhabricatorFileStorageEngine::loadWritableChunkEngines(); $chunk_engine_active = (bool) $engines; $this->checkS3(); if (!$chunk_engine_active) { $doc_href = PhabricatorEnv::getDocLink('Configuring File Storage'); $message = pht('Large file storage has not been configured, which will limit ' . 'the maximum size of file uploads. See %s for ' . 'instructions on configuring uploads and storage.', phutil_tag('a', array('href' => $doc_href, 'target' => '_blank'), pht('Configuring File Storage'))); $this->newIssue('large-files')->setShortName(pht('Large Files'))->setName(pht('Large File Storage Not Configured'))->setMessage($message); } $post_max_size = ini_get('post_max_size'); if ($post_max_size && (int) $post_max_size > 0) { $post_max_bytes = phutil_parse_bytes($post_max_size); $post_max_need = 32 * 1024 * 1024; if ($post_max_need > $post_max_bytes) { $summary = pht('Set %s in your PHP configuration to at least 32MB ' . 'to support large file uploads.', phutil_tag('tt', array(), 'post_max_size')); $message = pht('Adjust %s in your PHP configuration to at least 32MB. When ' . 'set to smaller value, large file uploads may not work properly.', phutil_tag('tt', array(), 'post_max_size')); $this->newIssue('php.post_max_size')->setName(pht('PHP post_max_size Not Configured'))->setSummary($summary)->setMessage($message)->setGroup(self::GROUP_PHP)->addPHPConfig('post_max_size'); } } // This is somewhat arbitrary, but make sure we have enough headroom to // upload a default file at the chunk threshold (8MB), which may be // base64 encoded, then JSON encoded in the request, and may need to be // held in memory in the raw and as a query string. $need_bytes = 64 * 1024 * 1024; $memory_limit = PhabricatorStartup::getOldMemoryLimit(); if ($memory_limit && (int) $memory_limit > 0) { $memory_limit_bytes = phutil_parse_bytes($memory_limit); $memory_usage_bytes = memory_get_usage(); $available_bytes = $memory_limit_bytes - $memory_usage_bytes; if ($need_bytes > $available_bytes) { $summary = pht('Your PHP memory limit is configured in a way that may prevent ' . 'you from uploading large files or handling large requests.'); $message = pht('When you upload a file via drag-and-drop or the API, chunks must ' . 'be buffered into memory before being written to permanent ' . 'storage. Phabricator needs memory available to store these ' . 'chunks while they are uploaded, but PHP is currently configured ' . 'to severly limit the available memory.' . "\n\n" . 'PHP processes currently have very little free memory available ' . '(%s). To work well, processes should have at least %s.' . "\n\n" . '(Note that the application itself must also fit in available ' . 'memory, so not all of the memory under the memory limit is ' . 'available for running workloads.)' . "\n\n" . "The easiest way to resolve this issue is to set %s to %s in your " . "PHP configuration, to disable the memory limit. There is " . "usually little or no value to using this option to limit " . "Phabricator process memory." . "\n\n" . "You can also increase the limit or ignore this issue and accept " . "that you may encounter problems uploading large files and " . "processing large requests.", phutil_format_bytes($available_bytes), phutil_format_bytes($need_bytes), phutil_tag('tt', array(), 'memory_limit'), phutil_tag('tt', array(), '-1')); $this->newIssue('php.memory_limit.upload')->setName(pht('Memory Limit Restricts File Uploads'))->setSummary($summary)->setMessage($message)->setGroup(self::GROUP_PHP)->addPHPConfig('memory_limit')->addPHPConfigOriginalValue('memory_limit', $memory_limit); } } $local_path = PhabricatorEnv::getEnvConfig('storage.local-disk.path'); if (!$local_path) { return; } if (!Filesystem::pathExists($local_path) || !is_readable($local_path) || !is_writable($local_path)) { $message = pht('Configured location for storing uploaded files on disk ("%s") does ' . 'not exist, or is not readable or writable. Verify the directory ' . 'exists and is readable and writable by the webserver.', $local_path); $this->newIssue('config.storage.local-disk.path')->setShortName(pht('Local Disk Storage'))->setName(pht('Local Disk Storage Not Readable/Writable'))->setMessage($message)->addPhabricatorConfig('storage.local-disk.path'); } }
public function buildConfigurationPagePanel() { $viewer = $this->getViewer(); $application = $this->getApplication(); $engines = PhabricatorFileStorageEngine::loadAllEngines(); $writable_engines = PhabricatorFileStorageEngine::loadWritableEngines(); $chunk_engines = PhabricatorFileStorageEngine::loadWritableChunkEngines(); $yes = pht('Yes'); $no = pht('No'); $rows = array(); $rowc = array(); foreach ($engines as $key => $engine) { $limited = $no; $limit = null; if ($engine->hasFilesizeLimit()) { $limited = $yes; $limit = phutil_format_bytes($engine->getFilesizeLimit()); } if ($engine->canWriteFiles()) { $writable = $yes; } else { $writable = $no; } if ($engine->isTestEngine()) { $test = $yes; } else { $test = $no; } if (isset($writable_engines[$key]) || isset($chunk_engines[$key])) { $rowc[] = 'highlighted'; } else { $rowc[] = null; } $rows[] = array($key, get_class($engine), $test, $writable, $limited, $limit); } $table = id(new AphrontTableView($rows))->setNoDataString(pht('No storage engines available.'))->setHeaders(array(pht('Key'), pht('Class'), pht('Unit Test'), pht('Writable'), pht('Has Limit'), pht('Limit')))->setRowClasses($rowc)->setColumnClasses(array('', 'wide', '', '', '', 'n')); $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Storage Engines'))->setTable($table); return $box; }
public function execute(PhutilArgumentParser $args) { $target_key = $args->getArg('engine'); if (!$target_key) { throw new PhutilArgumentUsageException(pht('Specify an engine to migrate to with `%s`. ' . 'Use `%s` to get a list of engines.', '--engine', 'files engines')); } $target_engine = PhabricatorFile::buildEngine($target_key); $iterator = $this->buildIterator($args); if (!$iterator) { throw new PhutilArgumentUsageException(pht('Either specify a list of files to migrate, or use `%s` ' . 'to migrate all files.', '--all')); } $is_dry_run = $args->getArg('dry-run'); $min_size = (int) $args->getArg('min-size'); $max_size = (int) $args->getArg('max-size'); $is_copy = $args->getArg('copy'); $failed = array(); $engines = PhabricatorFileStorageEngine::loadAllEngines(); $total_bytes = 0; $total_files = 0; foreach ($iterator as $file) { $monogram = $file->getMonogram(); $engine_key = $file->getStorageEngine(); $engine = idx($engines, $engine_key); if (!$engine) { echo tsprintf("%s\n", pht('%s: Uses unknown storage engine "%s".', $monogram, $engine_key)); $failed[] = $file; continue; } if ($engine->isChunkEngine()) { echo tsprintf("%s\n", pht('%s: Stored as chunks, no data to migrate directly.', $monogram)); continue; } if ($engine_key === $target_key) { echo tsprintf("%s\n", pht('%s: Already stored in engine "%s".', $monogram, $target_key)); continue; } $byte_size = $file->getByteSize(); if ($min_size && $byte_size < $min_size) { echo tsprintf("%s\n", pht('%s: File size (%s) is smaller than minimum size (%s).', $monogram, phutil_format_bytes($byte_size), phutil_format_bytes($min_size))); continue; } if ($max_size && $byte_size > $max_size) { echo tsprintf("%s\n", pht('%s: File size (%s) is larger than maximum size (%s).', $monogram, phutil_format_bytes($byte_size), phutil_format_bytes($max_size))); continue; } if ($is_dry_run) { echo tsprintf("%s\n", pht('%s: (%s) Would migrate from "%s" to "%s" (dry run)...', $monogram, phutil_format_bytes($byte_size), $engine_key, $target_key)); } else { echo tsprintf("%s\n", pht('%s: (%s) Migrating from "%s" to "%s"...', $monogram, phutil_format_bytes($byte_size), $engine_key, $target_key)); } try { if ($is_dry_run) { // Do nothing, this is a dry run. } else { $file->migrateToEngine($target_engine, $is_copy); } $total_files += 1; $total_bytes += $byte_size; echo tsprintf("%s\n", pht('Done.')); } catch (Exception $ex) { echo tsprintf("%s\n", pht('Failed! %s', (string) $ex)); $failed[] = $file; throw $ex; } } echo tsprintf("%s\n", pht('Total Migrated Files: %s', new PhutilNumber($total_files))); echo tsprintf("%s\n", pht('Total Migrated Bytes: %s', phutil_format_bytes($total_bytes))); if ($is_dry_run) { echo tsprintf("%s\n", pht('This was a dry run, so no real migrations were performed.')); } if ($failed) { $monograms = mpull($failed, 'getMonogram'); echo tsprintf("%s\n", pht('Failures: %s.', implode(', ', $monograms))); return 1; } return 0; }
private function browseFile() { $viewer = $this->getViewer(); $request = $this->getRequest(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $before = $request->getStr('before'); if ($before) { return $this->buildBeforeResponse($before); } $path = $drequest->getPath(); $preferences = $viewer->loadPreferences(); $show_blame = $request->getBool('blame', $preferences->getPreference(PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, false)); $show_color = $request->getBool('color', $preferences->getPreference(PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, true)); $view = $request->getStr('view'); if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) { $preferences->setPreference(PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, $show_blame); $preferences->setPreference(PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, $show_color); $preferences->save(); $uri = $request->getRequestURI()->alter('blame', null)->alter('color', null); return id(new AphrontRedirectResponse())->setURI($uri); } // We need the blame information if blame is on and we're building plain // text, or blame is on and this is an Ajax request. If blame is on and // this is a colorized request, we don't show blame at first (we ajax it // in afterward) so we don't need to query for it. $needs_blame = $show_blame && !$show_color || $show_blame && $request->isAjax(); $params = array('commit' => $drequest->getCommit(), 'path' => $drequest->getPath()); $byte_limit = null; if ($view !== 'raw') { $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); $time_limit = 10; $params += array('timeout' => $time_limit, 'byteLimit' => $byte_limit); } $response = $this->callConduitWithDiffusionRequest('diffusion.filecontentquery', $params); $hit_byte_limit = $response['tooHuge']; $hit_time_limit = $response['tooSlow']; $file_phid = $response['filePHID']; if ($hit_byte_limit) { $corpus = $this->buildErrorCorpus(pht('This file is larger than %s byte(s), and too large to display ' . 'in the web UI.', phutil_format_bytes($byte_limit))); } else { if ($hit_time_limit) { $corpus = $this->buildErrorCorpus(pht('This file took too long to load from the repository (more than ' . '%s second(s)).', new PhutilNumber($time_limit))); } else { $file = id(new PhabricatorFileQuery())->setViewer($viewer)->withPHIDs(array($file_phid))->executeOne(); if (!$file) { throw new Exception(pht('Failed to load content file!')); } if ($view === 'raw') { return $file->getRedirectResponse(); } $data = $file->loadFileData(); if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { $file_uri = $file->getBestURI(); if ($file->isViewableImage()) { $corpus = $this->buildImageCorpus($file_uri); } else { $corpus = $this->buildBinaryCorpus($file_uri, $data); } } else { $this->loadLintMessages(); $this->coverage = $drequest->loadCoverage(); // Build the content of the file. $corpus = $this->buildCorpus($show_blame, $show_color, $data, $needs_blame, $drequest, $path, $data); } } } if ($request->isAjax()) { return id(new AphrontAjaxResponse())->setContent($corpus); } require_celerity_resource('diffusion-source-css'); // Render the page. $view = $this->buildActionView($drequest); $action_list = $this->enrichActionView($view, $drequest, $show_blame, $show_color); $properties = $this->buildPropertyView($drequest, $action_list); $object_box = id(new PHUIObjectBoxView())->setHeader($this->buildHeaderView($drequest))->addPropertyList($properties); $content = array(); $content[] = $object_box; $follow = $request->getStr('follow'); if ($follow) { $notice = new PHUIInfoView(); $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); $notice->setTitle(pht('Unable to Continue')); switch ($follow) { case 'first': $notice->appendChild(pht('Unable to continue tracing the history of this file because ' . 'this commit is the first commit in the repository.')); break; case 'created': $notice->appendChild(pht('Unable to continue tracing the history of this file because ' . 'this commit created the file.')); break; } $content[] = $notice; } $renamed = $request->getStr('renamed'); if ($renamed) { $notice = new PHUIInfoView(); $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $notice->setTitle(pht('File Renamed')); $notice->appendChild(pht('File history passes through a rename from "%s" to "%s".', $drequest->getPath(), $renamed)); $content[] = $notice; } $content[] = $corpus; $content[] = $this->buildOpenRevisions(); $crumbs = $this->buildCrumbs(array('branch' => true, 'path' => true, 'view' => 'browse')); $basename = basename($this->getDiffusionRequest()->getPath()); return $this->newPage()->setTitle(array($basename, $repository->getDisplayName()))->setCrumbs($crumbs)->appendChild($content); }
protected function renderInput() { $file_id = $this->getID(); Javelin::initBehavior('phui-file-upload', array('fileInputID' => $file_id, 'inputName' => $this->getName(), 'uploadURI' => '/file/dropupload/', 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold())); return phutil_tag('input', array('type' => 'file', 'multiple' => $this->getAllowMultiple() ? 'multiple' : null, 'name' => $this->getName() . '.raw', 'id' => $file_id, 'disabled' => $this->getDisabled() ? 'disabled' : null)); }
protected function renderInput() { $id = $this->getID(); if (!$id) { $id = celerity_generate_unique_node_id(); $this->setID($id); } $viewer = $this->getUser(); if (!$viewer) { throw new PhutilInvalidStateException('setUser'); } // We need to have this if previews render images, since Ajax can not // currently ship JS or CSS. require_celerity_resource('lightbox-attachment-css'); Javelin::initBehavior('aphront-drag-and-drop-textarea', array('target' => $id, 'activatedClass' => 'aphront-textarea-drag-and-drop', 'uri' => '/file/dropupload/', 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold())); Javelin::initBehavior('phabricator-remarkup-assist', array('pht' => array('bold text' => pht('bold text'), 'italic text' => pht('italic text'), 'monospaced text' => pht('monospaced text'), 'List Item' => pht('List Item'), 'Quoted Text' => pht('Quoted Text'), 'data' => pht('data'), 'name' => pht('name'), 'URL' => pht('URL')))); Javelin::initBehavior('phabricator-tooltips', array()); $actions = array('fa-bold' => array('tip' => pht('Bold')), 'fa-italic' => array('tip' => pht('Italics')), 'fa-text-width' => array('tip' => pht('Monospaced')), 'fa-link' => array('tip' => pht('Link')), array('spacer' => true), 'fa-list-ul' => array('tip' => pht('Bulleted List')), 'fa-list-ol' => array('tip' => pht('Numbered List')), 'fa-code' => array('tip' => pht('Code Block')), 'fa-quote-right' => array('tip' => pht('Quote')), 'fa-table' => array('tip' => pht('Table')), 'fa-cloud-upload' => array('tip' => pht('Upload File'))); $can_use_macros = !$this->disableMacro && function_exists('imagettftext'); if ($can_use_macros) { $can_use_macros = PhabricatorApplication::isClassInstalledForViewer('PhabricatorMacroApplication', $viewer); } if ($can_use_macros) { $actions[] = array('spacer' => true); $actions['fa-meh-o'] = array('tip' => pht('Meme')); } $actions['fa-life-bouy'] = array('tip' => pht('Help'), 'align' => 'right', 'href' => PhrictionDocument::getSlugURI('usage/formatting_reference')); if (!$this->disableFullScreen) { $actions[] = array('spacer' => true, 'align' => 'right'); $actions['fa-arrows-alt'] = array('tip' => pht('Fullscreen Mode'), 'align' => 'right'); } $buttons = array(); foreach ($actions as $action => $spec) { $classes = array(); if (idx($spec, 'align') == 'right') { $classes[] = 'remarkup-assist-right'; } if (idx($spec, 'spacer')) { $classes[] = 'remarkup-assist-separator'; $buttons[] = phutil_tag('span', array('class' => implode(' ', $classes)), ''); continue; } else { $classes[] = 'remarkup-assist-button'; } $href = idx($spec, 'href', '#'); if ($href == '#') { $meta = array('action' => $action); $mustcapture = true; $target = null; } else { $meta = array(); $mustcapture = null; $target = '_blank'; } $content = null; $tip = idx($spec, 'tip'); if ($tip) { $meta['tip'] = $tip; $content = javelin_tag('span', array('aural' => true), $tip); } $buttons[] = javelin_tag('a', array('class' => implode(' ', $classes), 'href' => $href, 'sigil' => 'remarkup-assist has-tooltip', 'meta' => $meta, 'mustcapture' => $mustcapture, 'target' => $target, 'tabindex' => -1), phutil_tag('div', array('class' => 'remarkup-assist phui-icon-view phui-font-fa bluegrey ' . $action), $content)); } $buttons = phutil_tag('div', array('class' => 'remarkup-assist-bar'), $buttons); $monospaced_textareas = null; $monospaced_textareas_class = null; $monospaced_textareas = $viewer->loadPreferences()->getPreference(PhabricatorUserPreferences::PREFERENCE_MONOSPACED_TEXTAREAS); if ($monospaced_textareas == 'enabled') { $monospaced_textareas_class = 'PhabricatorMonospaced'; } $this->setCustomClass('remarkup-assist-textarea ' . $monospaced_textareas_class); return javelin_tag('div', array('sigil' => 'remarkup-assist-control'), array($buttons, parent::renderInput())); }
public function handleRequest(AphrontRequest $request) { $viewer = $request->getUser(); $response = $this->loadProject(); if ($response) { return $response; } $project = $this->getProject(); $this->readRequestState(); $board_uri = $this->getApplicationURI('board/' . $project->getID() . '/'); $search_engine = id(new ManiphestTaskSearchEngine())->setViewer($viewer)->setBaseURI($board_uri)->setIsBoardView(true); if ($request->isFormPost() && !$request->getBool('initialize')) { $saved = $search_engine->buildSavedQueryFromRequest($request); $search_engine->saveQuery($saved); $filter_form = id(new AphrontFormView())->setUser($viewer); $search_engine->buildSearchForm($filter_form, $saved); if ($search_engine->getErrors()) { return $this->newDialog()->setWidth(AphrontDialogView::WIDTH_FULL)->setTitle(pht('Advanced Filter'))->appendChild($filter_form->buildLayoutView())->setErrors($search_engine->getErrors())->setSubmitURI($board_uri)->addSubmitButton(pht('Apply Filter'))->addCancelButton($board_uri); } return id(new AphrontRedirectResponse())->setURI($this->getURIWithState($search_engine->getQueryResultsPageURI($saved->getQueryKey()))); } $query_key = $request->getURIData('queryKey'); if (!$query_key) { $query_key = 'open'; } $this->queryKey = $query_key; $custom_query = null; if ($search_engine->isBuiltinQuery($query_key)) { $saved = $search_engine->buildSavedQueryFromBuiltin($query_key); } else { $saved = id(new PhabricatorSavedQueryQuery())->setViewer($viewer)->withQueryKeys(array($query_key))->executeOne(); if (!$saved) { return new Aphront404Response(); } $custom_query = $saved; } if ($request->getURIData('filter')) { $filter_form = id(new AphrontFormView())->setUser($viewer); $search_engine->buildSearchForm($filter_form, $saved); return $this->newDialog()->setWidth(AphrontDialogView::WIDTH_FULL)->setTitle(pht('Advanced Filter'))->appendChild($filter_form->buildLayoutView())->setSubmitURI($board_uri)->addSubmitButton(pht('Apply Filter'))->addCancelButton($board_uri); } $task_query = $search_engine->buildQueryFromSavedQuery($saved); $select_phids = array($project->getPHID()); if ($project->getHasSubprojects() || $project->getHasMilestones()) { $descendants = id(new PhabricatorProjectQuery())->setViewer($viewer)->withAncestorProjectPHIDs($select_phids)->execute(); foreach ($descendants as $descendant) { $select_phids[] = $descendant->getPHID(); } } $tasks = $task_query->withEdgeLogicPHIDs(PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, PhabricatorQueryConstraint::OPERATOR_ANCESTOR, array($select_phids))->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)->setViewer($viewer)->execute(); $tasks = mpull($tasks, null, 'getPHID'); $board_phid = $project->getPHID(); $layout_engine = id(new PhabricatorBoardLayoutEngine())->setViewer($viewer)->setBoardPHIDs(array($board_phid))->setObjectPHIDs(array_keys($tasks))->setFetchAllBoards(true)->executeLayout(); $columns = $layout_engine->getColumns($board_phid); if (!$columns || !$project->getHasWorkboard()) { $has_normal_columns = false; foreach ($columns as $column) { if (!$column->getProxyPHID()) { $has_normal_columns = true; break; } } $can_edit = PhabricatorPolicyFilter::hasCapability($viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); if (!$has_normal_columns) { if (!$can_edit) { $content = $this->buildNoAccessContent($project); } else { $content = $this->buildInitializeContent($project); } } else { if (!$can_edit) { $content = $this->buildDisabledContent($project); } else { $content = $this->buildEnableContent($project); } } if ($content instanceof AphrontResponse) { return $content; } $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::PANEL_WORKBOARD); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard')); return $this->newPage()->setTitle(array($project->getDisplayName(), pht('Workboard')))->setNavigation($nav)->setCrumbs($crumbs)->appendChild($content); } $task_can_edit_map = id(new PhabricatorPolicyFilter())->setViewer($viewer)->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))->apply($tasks); // If this is a batch edit, select the editable tasks in the chosen column // and ship the user into the batch editor. $batch_edit = $request->getStr('batch'); if ($batch_edit) { if ($batch_edit !== self::BATCH_EDIT_ALL) { $column_id_map = mpull($columns, null, 'getID'); $batch_column = idx($column_id_map, $batch_edit); if (!$batch_column) { return new Aphront404Response(); } $batch_task_phids = $layout_engine->getColumnObjectPHIDs($board_phid, $batch_column->getPHID()); foreach ($batch_task_phids as $key => $batch_task_phid) { if (empty($task_can_edit_map[$batch_task_phid])) { unset($batch_task_phids[$key]); } } $batch_tasks = array_select_keys($tasks, $batch_task_phids); } else { $batch_tasks = $task_can_edit_map; } if (!$batch_tasks) { $cancel_uri = $this->getURIWithState($board_uri); return $this->newDialog()->setTitle(pht('No Editable Tasks'))->appendParagraph(pht('The selected column contains no visible tasks which you ' . 'have permission to edit.'))->addCancelButton($board_uri); } $batch_ids = mpull($batch_tasks, 'getID'); $batch_ids = implode(',', $batch_ids); $batch_uri = new PhutilURI('/maniphest/batch/'); $batch_uri->setQueryParam('board', $this->id); $batch_uri->setQueryParam('batch', $batch_ids); return id(new AphrontRedirectResponse())->setURI($batch_uri); } $board_id = celerity_generate_unique_node_id(); $board = id(new PHUIWorkboardView())->setUser($viewer)->setID($board_id)->addSigil('jx-workboard')->setMetadata(array('boardPHID' => $project->getPHID())); $visible_columns = array(); $column_phids = array(); $visible_phids = array(); foreach ($columns as $column) { if (!$this->showHidden) { if ($column->isHidden()) { continue; } } $proxy = $column->getProxy(); if ($proxy && !$proxy->isMilestone()) { // TODO: For now, don't show subproject columns because we can't // handle tasks with multiple positions yet. continue; } $task_phids = $layout_engine->getColumnObjectPHIDs($board_phid, $column->getPHID()); $column_tasks = array_select_keys($tasks, $task_phids); // If we aren't using "natural" order, reorder the column by the original // query order. if ($this->sortKey != PhabricatorProjectColumn::ORDER_NATURAL) { $column_tasks = array_select_keys($column_tasks, array_keys($tasks)); } $column_phid = $column->getPHID(); $visible_columns[$column_phid] = $column; $column_phids[$column_phid] = $column_tasks; foreach ($column_tasks as $phid => $task) { $visible_phids[$phid] = $phid; } } $rendering_engine = id(new PhabricatorBoardRenderingEngine())->setViewer($viewer)->setObjects(array_select_keys($tasks, $visible_phids))->setEditMap($task_can_edit_map)->setExcludedProjectPHIDs($select_phids); $templates = array(); $column_maps = array(); $all_tasks = array(); foreach ($visible_columns as $column_phid => $column) { $column_tasks = $column_phids[$column_phid]; $panel = id(new PHUIWorkpanelView())->setHeader($column->getDisplayName())->setSubHeader($column->getDisplayType())->addSigil('workpanel'); $proxy = $column->getProxy(); if ($proxy) { $proxy_id = $proxy->getID(); $href = $this->getApplicationURI("view/{$proxy_id}/"); $panel->setHref($href); } $header_icon = $column->getHeaderIcon(); if ($header_icon) { $panel->setHeaderIcon($header_icon); } $display_class = $column->getDisplayClass(); if ($display_class) { $panel->addClass($display_class); } if ($column->isHidden()) { $panel->addClass('project-panel-hidden'); } $column_menu = $this->buildColumnMenu($project, $column); $panel->addHeaderAction($column_menu); $count_tag = id(new PHUITagView())->setType(PHUITagView::TYPE_SHADE)->setShade(PHUITagView::COLOR_BLUE)->addSigil('column-points')->setName(javelin_tag('span', array('sigil' => 'column-points-content'), pht('-')))->setStyle('display: none'); $panel->setHeaderTag($count_tag); $cards = id(new PHUIObjectItemListView())->setUser($viewer)->setFlush(true)->setAllowEmptyList(true)->addSigil('project-column')->setItemClass('phui-workcard')->setMetadata(array('columnPHID' => $column->getPHID(), 'pointLimit' => $column->getPointLimit())); foreach ($column_tasks as $task) { $object_phid = $task->getPHID(); $card = $rendering_engine->renderCard($object_phid); $templates[$object_phid] = hsprintf('%s', $card->getItem()); $column_maps[$column_phid][] = $object_phid; $all_tasks[$object_phid] = $task; } $panel->setCards($cards); $board->addPanel($panel); } $behavior_config = array('moveURI' => $this->getApplicationURI('move/' . $project->getID() . '/'), 'createURI' => $this->getCreateURI(), 'uploadURI' => '/file/dropupload/', 'coverURI' => $this->getApplicationURI('cover/'), 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), 'pointsEnabled' => ManiphestTaskPoints::getIsEnabled(), 'boardPHID' => $project->getPHID(), 'order' => $this->sortKey, 'templateMap' => $templates, 'columnMaps' => $column_maps, 'orderMaps' => mpull($all_tasks, 'getWorkboardOrderVectors'), 'propertyMaps' => mpull($all_tasks, 'getWorkboardProperties'), 'boardID' => $board_id, 'projectPHID' => $project->getPHID()); $this->initBehavior('project-boards', $behavior_config); $sort_menu = $this->buildSortMenu($viewer, $this->sortKey); $filter_menu = $this->buildFilterMenu($viewer, $custom_query, $search_engine, $query_key); $manage_menu = $this->buildManageMenu($project, $this->showHidden); $header_link = phutil_tag('a', array('href' => $this->getApplicationURI('profile/' . $project->getID() . '/')), $project->getName()); $board_box = id(new PHUIBoxView())->appendChild($board)->addClass('project-board-wrapper'); $nav = $this->getProfileMenu(); $divider = id(new PHUIListItemView())->setType(PHUIListItemView::TYPE_DIVIDER); $fullscreen = $this->buildFullscreenMenu(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard')); $crumbs->setBorder(true); $crumbs->addAction($sort_menu); $crumbs->addAction($filter_menu); $crumbs->addAction($divider); $crumbs->addAction($manage_menu); $crumbs->addAction($fullscreen); return $this->newPage()->setTitle(array($project->getDisplayName(), pht('Workboard')))->setPageObjectPHIDs(array($project->getPHID()))->setShowFooter(false)->setNavigation($nav)->setCrumbs($crumbs)->addQuicksandConfig(array('boardConfig' => $behavior_config))->appendChild(array($board_box)); }
private function browseFile() { $viewer = $this->getViewer(); $request = $this->getRequest(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $before = $request->getStr('before'); if ($before) { return $this->buildBeforeResponse($before); } $path = $drequest->getPath(); $blame_key = PhabricatorDiffusionBlameSetting::SETTINGKEY; $color_key = PhabricatorDiffusionColorSetting::SETTINGKEY; $show_blame = $request->getBool('blame', $viewer->getUserSetting($blame_key)); $show_color = $request->getBool('color', $viewer->getUserSetting($color_key)); $view = $request->getStr('view'); if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) { $preferences = PhabricatorUserPreferences::loadUserPreferences($viewer); $editor = id(new PhabricatorUserPreferencesEditor())->setActor($viewer)->setContentSourceFromRequest($request)->setContinueOnNoEffect(true)->setContinueOnMissingFields(true); $xactions = array(); $xactions[] = $preferences->newTransaction($blame_key, $show_blame); $xactions[] = $preferences->newTransaction($color_key, $show_color); $editor->applyTransactions($preferences, $xactions); $uri = $request->getRequestURI()->alter('blame', null)->alter('color', null); return id(new AphrontRedirectResponse())->setURI($uri); } // We need the blame information if blame is on and we're building plain // text, or blame is on and this is an Ajax request. If blame is on and // this is a colorized request, we don't show blame at first (we ajax it // in afterward) so we don't need to query for it. $needs_blame = $show_blame && !$show_color || $show_blame && $request->isAjax(); $params = array('commit' => $drequest->getCommit(), 'path' => $drequest->getPath()); $byte_limit = null; if ($view !== 'raw') { $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); $time_limit = 10; $params += array('timeout' => $time_limit, 'byteLimit' => $byte_limit); } $response = $this->callConduitWithDiffusionRequest('diffusion.filecontentquery', $params); $hit_byte_limit = $response['tooHuge']; $hit_time_limit = $response['tooSlow']; $file_phid = $response['filePHID']; if ($hit_byte_limit) { $corpus = $this->buildErrorCorpus(pht('This file is larger than %s byte(s), and too large to display ' . 'in the web UI.', phutil_format_bytes($byte_limit))); } else { if ($hit_time_limit) { $corpus = $this->buildErrorCorpus(pht('This file took too long to load from the repository (more than ' . '%s second(s)).', new PhutilNumber($time_limit))); } else { $file = id(new PhabricatorFileQuery())->setViewer($viewer)->withPHIDs(array($file_phid))->executeOne(); if (!$file) { throw new Exception(pht('Failed to load content file!')); } if ($view === 'raw') { return $file->getRedirectResponse(); } $data = $file->loadFileData(); $ref = $this->getGitLFSRef($repository, $data); if ($ref) { if ($view == 'git-lfs') { $file = $this->loadGitLFSFile($ref); // Rename the file locally so we generate a better vanity URI for // it. In storage, it just has a name like "lfs-13f9a94c0923...", // since we don't get any hints about possible human-readable names // at upload time. $basename = basename($drequest->getPath()); $file->makeEphemeral(); $file->setName($basename); return $file->getRedirectResponse(); } else { $corpus = $this->buildGitLFSCorpus($ref); } } else { if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { $file_uri = $file->getBestURI(); if ($file->isViewableImage()) { $corpus = $this->buildImageCorpus($file_uri); } else { $corpus = $this->buildBinaryCorpus($file_uri, $data); } } else { $this->loadLintMessages(); $this->coverage = $drequest->loadCoverage(); // Build the content of the file. $corpus = $this->buildCorpus($show_blame, $show_color, $data, $needs_blame, $drequest, $path, $data); } } } } if ($request->isAjax()) { return id(new AphrontAjaxResponse())->setContent($corpus); } require_celerity_resource('diffusion-source-css'); // Render the page. $view = $this->buildCurtain($drequest); $curtain = $this->enrichCurtain($view, $drequest, $show_blame, $show_color); $properties = $this->buildPropertyView($drequest); $header = $this->buildHeaderView($drequest); $header->setHeaderIcon('fa-file-code-o'); $content = array(); $follow = $request->getStr('follow'); if ($follow) { $notice = new PHUIInfoView(); $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); $notice->setTitle(pht('Unable to Continue')); switch ($follow) { case 'first': $notice->appendChild(pht('Unable to continue tracing the history of this file because ' . 'this commit is the first commit in the repository.')); break; case 'created': $notice->appendChild(pht('Unable to continue tracing the history of this file because ' . 'this commit created the file.')); break; } $content[] = $notice; } $renamed = $request->getStr('renamed'); if ($renamed) { $notice = new PHUIInfoView(); $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $notice->setTitle(pht('File Renamed')); $notice->appendChild(pht('File history passes through a rename from "%s" to "%s".', $drequest->getPath(), $renamed)); $content[] = $notice; } $content[] = $corpus; $content[] = $this->buildOpenRevisions(); $crumbs = $this->buildCrumbs(array('branch' => true, 'path' => true, 'view' => 'browse')); $crumbs->setBorder(true); $basename = basename($this->getDiffusionRequest()->getPath()); $view = id(new PHUITwoColumnView())->setHeader($header)->setCurtain($curtain)->setMainColumn(array($content)); if ($properties) { $view->addPropertySection(pht('Details'), $properties); } $title = array($basename, $repository->getDisplayName()); return $this->newPage()->setTitle($title)->setCrumbs($crumbs)->appendChild(array($view)); }
/** * Find a storage engine which is suitable for storing chunks. * * This engine must be a writable engine, have a filesize limit larger than * the chunk limit, and must not be a chunk engine itself. */ private function getWritableEngine() { // NOTE: We can't just load writable engines or we'll loop forever. $engines = parent::loadAllEngines(); foreach ($engines as $engine) { if ($engine->isChunkEngine()) { continue; } if ($engine->isTestEngine()) { continue; } if (!$engine->canWriteFiles()) { continue; } if ($engine->hasFilesizeLimit()) { if ($engine->getFilesizeLimit() < $this->getChunkSize()) { continue; } } return true; } return false; }
private function getChunkEngine() { $chunk_engines = PhabricatorFileStorageEngine::loadWritableChunkEngines(); if (!$chunk_engines) { throw new Exception(pht('Unable to upload file: this server is not configured with any ' . 'storage engine which can store large files.')); } return head($chunk_engines); }
/** * Destroy stored file data if there are no remaining files which reference * it. */ public function deleteFileDataIfUnused(PhabricatorFileStorageEngine $engine, $engine_identifier, $handle) { // Check to see if any files are using storage. $usage = id(new PhabricatorFile())->loadAllWhere('storageEngine = %s AND storageHandle = %s LIMIT 1', $engine_identifier, $handle); // If there are no files using the storage, destroy the actual storage. if (!$usage) { try { $engine->deleteFile($handle); } catch (Exception $ex) { // In the worst case, we're leaving some data stranded in a storage // engine, which is not a big deal. phlog($ex); } } }
protected function renderInput() { $id = $this->getID(); if (!$id) { $id = celerity_generate_unique_node_id(); $this->setID($id); } $viewer = $this->getUser(); if (!$viewer) { throw new PhutilInvalidStateException('setUser'); } // We need to have this if previews render images, since Ajax can not // currently ship JS or CSS. require_celerity_resource('lightbox-attachment-css'); if (!$this->getDisabled()) { Javelin::initBehavior('aphront-drag-and-drop-textarea', array('target' => $id, 'activatedClass' => 'aphront-textarea-drag-and-drop', 'uri' => '/file/dropupload/', 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold())); } $root_id = celerity_generate_unique_node_id(); $user_datasource = new PhabricatorPeopleDatasource(); $proj_datasource = id(new PhabricatorProjectDatasource())->setParameters(array('autocomplete' => 1)); Javelin::initBehavior('phabricator-remarkup-assist', array('pht' => array('bold text' => pht('bold text'), 'italic text' => pht('italic text'), 'monospaced text' => pht('monospaced text'), 'List Item' => pht('List Item'), 'Quoted Text' => pht('Quoted Text'), 'data' => pht('data'), 'name' => pht('name'), 'URL' => pht('URL')), 'disabled' => $this->getDisabled(), 'rootID' => $root_id, 'autocompleteMap' => (object) array(64 => array('datasourceURI' => $user_datasource->getDatasourceURI(), 'headerIcon' => 'fa-user', 'headerText' => pht('Find User:'******'hintText' => $user_datasource->getPlaceholderText()), 35 => array('datasourceURI' => $proj_datasource->getDatasourceURI(), 'headerIcon' => 'fa-briefcase', 'headerText' => pht('Find Project:'), 'hintText' => $proj_datasource->getPlaceholderText())))); Javelin::initBehavior('phabricator-tooltips', array()); $actions = array('fa-bold' => array('tip' => pht('Bold'), 'nodevice' => true), 'fa-italic' => array('tip' => pht('Italics'), 'nodevice' => true), 'fa-text-width' => array('tip' => pht('Monospaced'), 'nodevice' => true), 'fa-link' => array('tip' => pht('Link'), 'nodevice' => true), array('spacer' => true, 'nodevice' => true), 'fa-list-ul' => array('tip' => pht('Bulleted List'), 'nodevice' => true), 'fa-list-ol' => array('tip' => pht('Numbered List'), 'nodevice' => true), 'fa-code' => array('tip' => pht('Code Block'), 'nodevice' => true), 'fa-quote-right' => array('tip' => pht('Quote'), 'nodevice' => true), 'fa-table' => array('tip' => pht('Table'), 'nodevice' => true), 'fa-cloud-upload' => array('tip' => pht('Upload File'))); $can_use_macros = !$this->disableMacro && function_exists('imagettftext'); if ($can_use_macros) { $can_use_macros = PhabricatorApplication::isClassInstalledForViewer('PhabricatorMacroApplication', $viewer); } if ($can_use_macros) { $actions[] = array('spacer' => true); $actions['fa-meh-o'] = array('tip' => pht('Meme')); } $actions['fa-eye'] = array('tip' => pht('Preview'), 'align' => 'right'); $actions[] = array('spacer' => true, 'align' => 'right'); $actions['fa-life-bouy'] = array('tip' => pht('Help'), 'align' => 'right', 'href' => PhabricatorEnv::getDoclink('Remarkup Reference')); if (!$this->disableFullScreen) { $actions[] = array('spacer' => true, 'align' => 'right'); $actions['fa-arrows-alt'] = array('tip' => pht('Fullscreen Mode'), 'align' => 'right'); } $buttons = array(); foreach ($actions as $action => $spec) { $classes = array(); if (idx($spec, 'align') == 'right') { $classes[] = 'remarkup-assist-right'; } if (idx($spec, 'nodevice')) { $classes[] = 'remarkup-assist-nodevice'; } if (idx($spec, 'spacer')) { $classes[] = 'remarkup-assist-separator'; $buttons[] = phutil_tag('span', array('class' => implode(' ', $classes)), ''); continue; } else { $classes[] = 'remarkup-assist-button'; } $href = idx($spec, 'href', '#'); if ($href == '#') { $meta = array('action' => $action); $mustcapture = true; $target = null; } else { $meta = array(); $mustcapture = null; $target = '_blank'; } $content = null; $tip = idx($spec, 'tip'); if ($tip) { $meta['tip'] = $tip; $content = javelin_tag('span', array('aural' => true), $tip); } $sigils = array(); $sigils[] = 'remarkup-assist'; if (!$this->getDisabled()) { $sigils[] = 'has-tooltip'; } $buttons[] = javelin_tag('a', array('class' => implode(' ', $classes), 'href' => $href, 'sigil' => implode(' ', $sigils), 'meta' => $meta, 'mustcapture' => $mustcapture, 'target' => $target, 'tabindex' => -1), phutil_tag('div', array('class' => 'remarkup-assist phui-icon-view phui-font-fa bluegrey ' . $action), $content)); } $buttons = phutil_tag('div', array('class' => 'remarkup-assist-bar'), $buttons); $use_monospaced = $viewer->compareUserSetting(PhabricatorMonospacedTextareasSetting::SETTINGKEY, PhabricatorMonospacedTextareasSetting::VALUE_TEXT_MONOSPACED); if ($use_monospaced) { $monospaced_textareas_class = 'PhabricatorMonospaced'; } else { $monospaced_textareas_class = null; } $this->setCustomClass('remarkup-assist-textarea ' . $monospaced_textareas_class); return javelin_tag('div', array('sigil' => 'remarkup-assist-control', 'class' => $this->getDisabled() ? 'disabled-control' : null, 'id' => $root_id), array($buttons, parent::renderInput())); }
public function testLoadAllEngines() { PhabricatorFileStorageEngine::loadAllEngines(); $this->assertTrue(true); }