public function test_copy_from_file() { $attachment = $this->create_object('midcom_db_attachment', array('parentguid' => self::$_topic->guid)); $stat = $attachment->copy_from_file(self::$_filepath . 'attach.png'); $this->assertTrue($stat, midcom_connection::get_error_string()); $blob = new midgard_blob($attachment->__object); $this->assertFileEquals(self::$_filepath . 'attach.png', $blob->get_path()); }
/** * @param mixed $handler_id The ID of the handler. * @param Array $args The argument list. * @param Array &$data The local request data. */ public function _handler_pdf($handler_id, array $args, array &$data) { $this->_invoice = new org_openpsa_invoices_invoice_dba($args[0]); $this->_request_data['invoice_url'] = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_ANCHORPREFIX) . "invoice/" . $this->_invoice->guid . "/"; //check for manually uploaded pdf-file & if user wants to replace it if (array_key_exists('cancel', $_POST)) { return new midcom_response_relocate($this->_request_data['invoice_url']); } else { if (array_key_exists('save', $_POST)) { $this->_update_attachment = true; } else { $data['confirmation_message'] = 'current pdf file was manually uploaded shall it be replaced ?'; // load schema & datamanager to get attachment $schemadb = midcom_helper_datamanager2_schema::load_database($this->_config->get('schemadb')); $this->_datamanager = new midcom_helper_datamanager2_datamanager($schemadb); if (!$this->_datamanager->autoset_storage($this->_invoice)) { throw new midcom_error("Failed to create a DM2 instance for object {$this->_invoice->guid}."); } if ($this->_invoice->sent) { $data['confirmation_message'] = 'invoice has already been sent. should it be replaced?'; } else { if (!empty($this->_datamanager->types['pdf_file']->attachments)) { foreach ($this->_datamanager->types['pdf_file']->attachments as $attachment) { $checksum = $attachment->get_parameter('org.openpsa.invoices', 'auto_generated'); // check if auto generated parameter is same as md5 in current-file // if not the file was manually uploaded if ($checksum) { $blob = new midgard_blob($attachment->__object); // check if md5 sum equals the one saved in auto_generated if ($checksum == md5_file($blob->get_path())) { $this->_update_attachment = true; } } } } else { $this->_update_attachment = true; } } } } if ($this->_update_attachment) { $this->_request_data['billing_data'] = $this->_invoice->get_billing_data(); if (self::render_and_attach_pdf($this->_invoice)) { midcom::get('uimessages')->add($this->_l10n->get($this->_component), $this->_l10n->get('pdf created')); } else { midcom::get('uimessages')->add($this->_l10n->get($this->_component), $this->_l10n->get('pdf creation failed') . ': ' . midcom_connection::get_error_string(), 'error'); } return new midcom_response_relocate($this->_request_data["invoice_url"]); } }
/** * Function serves the attachment by provided guid and exits. * @todo: Permission handling * @todo: Direct filesystem serving * @todo: Configuration options */ public function get_serve(array $args) { $att = new midgard_attachment($args['guid']); if (midgardmvc_core::get_instance()->configuration->enable_attachment_cache) { midgardmvc_core::get_instance()->dispatcher->header('Location: ' . midgardmvc_core_helpers_attachment::get_url($att)); midgardmvc_core::get_instance()->dispatcher->end_request(); } $blob = new midgard_blob($att); midgardmvc_core::get_instance()->dispatcher->header('Content-type: ' . $att->mimetype); /** * If X-Sendfile support is enabled just sending correct headers */ if (midgardmvc_core::get_instance()->configuration->enable_xsendfile) { midgardmvc_core::get_instance()->dispatcher->header('X-Sendfile: ' . $blob->get_path()); } else { echo $blob->read_content(); } midgardmvc_core::get_instance()->dispatcher->end_request(); }
/** * Links file to public web folder. * * @param midgard_attachment attachment An attachment object * @return true of false */ public static function add_to_cache(midgard_attachment &$attachment) { $blob = new midgard_blob($attachment); // FIXME: Attachment directory creating should be done more elegantly $attachment_dir = explode('/', $attachment->location); $attachment_dir = midgardmvc_core::get_instance()->configuration->attachment_cache_directory . "{$attachment_dir[0]}/{$attachment_dir[1]}/"; if (is_file(midgardmvc_core::get_instance()->configuration->attachment_cache_directory . $attachment->location)) { return false; } if (!is_dir($attachment_dir)) { mkdir($attachment_dir, 0700, true); } return symlink($blob->get_path(), midgardmvc_core::get_instance()->configuration->attachment_cache_directory . $attachment->location); }
/** * Simple wrapper for stat() on the blob object. * * @return mixed Either a stat array as for stat() or false on failure. */ function stat() { if (!$this->id) { debug_add('Cannot open a non-persistent attachment..', MIDCOM_LOG_WARN); debug_print_r('Object state:', $this); return false; } $blob = new midgard_blob($this->__object); $path = $blob->get_path(); if (!file_exists($path)) { debug_add("File {$path} that blob {$this->guid} points to cannot be found", MIDCOM_LOG_WARN); return false; } return stat($path); }
/** * Deliver a blob to the client. * * This is a replacement for mgd_serve_attachment that should work around most of * its bugs: It is missing all important HTTP Headers concerning file size, * modification date and expiration. It will not call _midcom_stop_request() when it is finished, * you still have to do that yourself. It will add the following HTTP Headers: * * - Cache-Control: public max-age=$expires * - Expires: GMT Date $now+$expires * - Last-Modified: GMT Date of the last modified timestamp of the Attachment * - Content-Length: The Length of the Attachment in Bytes * - Accept-Ranges: none * * This should enable caching of browsers for Navigation images and so on. You can * influence the expiration of the served attachment with the parameter $expires. * It is the time in seconds till the client should refetch the file. The default * for this is 24 hours. If you set it to "0" caching will be prohibited by * changing the sent headers like this: * * - Pragma: no-cache * - Cache-Control: no-cache * - Expires: Current GMT Date * * If expires is set to -1, no expires header gets sent. * * @param MidgardAttachment &$attachment A reference to the attachment to be delivered. * @param int $expires HTTP-Expires timeout in seconds, set this to 0 for uncacheable pages, or to -1 for no Expire header. */ public function serve_attachment(&$attachment, $expires = -1) { if ($GLOBALS['midcom_config']['attachment_cache_enabled']) { $path = '/' . substr($attachment->guid, 0, 1) . "/{$attachment->guid}_{$attachment->name}"; if (file_exists($GLOBALS['midcom_config']['attachment_cache_root'] . $path)) { $response = new midcom_response_relocate($GLOBALS['midcom_config']['attachment_cache_url'] . $path, 301); $response->send(); } } // Sanity check expires if (!is_int($expires) || $expires < -1) { throw new midcom_error("\$expires has to be a positive integer or zero or -1, is now {$expires}."); } // Doublecheck that this is registered $cache = midcom::get('cache'); $cache->content->register($attachment->guid); $stats = $attachment->stat(); $last_modified =& $stats[9]; $app = midcom::get(); $etag = md5("{$last_modified}{$attachment->name}{$attachment->mimetype}{$attachment->guid}"); // Check etag and return 304 if necessary if ($expires != 0 && $cache->content->_check_not_modified($last_modified, $etag)) { if (!_midcom_headers_sent()) { $cache->content->cache_control_headers(); // Doublemakesure these are present $app->header('HTTP/1.0 304 Not Modified', 304); $app->header("ETag: {$etag}"); } while (@ob_end_flush()) { } debug_add("End of MidCOM run: {$_SERVER['REQUEST_URI']}"); _midcom_stop_request(); } $f = $attachment->open('r'); if (!$f) { throw new midcom_error('Failed to open attachment for reading: ' . midcom_connection::get_error_string()); } $app->header("ETag: {$etag}"); $cache->content->content_type($attachment->mimetype); $cache->content->register_sent_header("Content-Type: {$attachment->mimetype}"); $app->header("Last-Modified: " . gmdate("D, d M Y H:i:s", $last_modified) . ' GMT'); $app->header("Content-Length: " . $stats[7]); $app->header("Content-Description: {$attachment->title}"); // PONDER: Support ranges ("continue download") somehow ? $app->header("Accept-Ranges: none"); if ($expires > 0) { // If custom expiry now+expires is set use that $cache->content->expires(time() + $expires); } else { if ($expires == 0) { // expires set to 0 means disable cache, so we shall $cache->content->no_cache(); } } // TODO: Check metadata service for the real expiry timestamp ? $cache->content->cache_control_headers(); $send_att_body = true; if ($GLOBALS['midcom_config']['attachment_xsendfile_enable']) { $blob = new midgard_blob($attachment->__object); $att_local_path = $blob->get_path(); debug_add("Checking is_readable({$att_local_path})"); if (is_readable($att_local_path)) { $app->header("X-Sendfile: {$att_local_path}"); $send_att_body = false; } } // Store metadata in cache so _check_hit() can help us if (!$cache->content->_uncached && !$cache->content->_no_cache) { $cache->content->write_meta_cache('A-' . $etag, $etag); } while (@ob_end_flush()) { } if (!$send_att_body) { debug_add('NOT sending file (X-Sendfile will take care of that, _midcom_stop_request()ing so nothing has a chance the mess things up anymore'); _midcom_stop_request(); } fpassthru($f); $attachment->close(); debug_add("End of MidCOM run: {$_SERVER['REQUEST_URI']}"); _midcom_stop_request(); }
/** * Creates or updates a package in the database * * @return object package object */ public function createPackage($project_name = null, $repo_id = null, $repo_name = null, $arch_name = null, $package_name = null, $file_name = null, $repo_arch_name = null) { if ($project_name && $repo_name && $arch_name && $package_name && $file_name) { // get fill package info via OBS API try { $extinfo = $this->api->getPackageWithFullInformation($project_name, $repo_name, $arch_name, $package_name, $file_name); } catch (RuntimeException $e) { $this->log(' [EXCEPTION] ' . $e->getMessage()); } } else { throw new RuntimeException('Not enough parameters to gather full package information'); return; } // get a com_meego_package instance $package = $this->getPackageByFileName($file_name, $repo_id); if ($package && $repo_id && $extinfo) { if (!$package->guid) { $package->repository = $repo_id; $package->filename = $extinfo->filename; } // deb or rpm $package->type = substr($package->filename, strrpos($package->filename, '.') + 1); $package->size = $extinfo->size; $package->name = $extinfo->name; $package->title = $extinfo->title; $package->parent = $package_name; $package->version = $extinfo->version; $summary_max_length = 100; if (array_key_exists('summary_max_length', $this->config)) { $summary_max_length = $this->config['summary_max_length']; } if (strlen($extinfo->summary)) { $package->summary = $this->generateAbstract($extinfo->summary, $summary_max_length); } else { $package->summary = $this->generateAbstract($extinfo->description, $summary_max_length); } $package->description = $extinfo->description; // if the package is a source package then the downloadurl is slightly different // also change the title a bit if ($repo_arch_name == 'src') { $package->title = $package->title . '-src'; } if ($repo_arch_name == 'armv7el') { // fix the inconsistency between the published repo and the API $repo_arch_name = 'armv7l'; if ($package->type == 'deb') { $repo_arch_name = $extinfo->arch; } } // direct download url $_uri = str_replace(':', ':/', $project_name) . '/' . str_replace(':', ':/', $repo_name) . '/' . $repo_arch_name . '/' . $file_name; $package->downloadurl = $this->download_repo_protocol . '://' . $this->download_repo_host . '/' . $_uri; // @todo $package->bugtracker = '* TODO *'; // for some info we need a special xray try { switch ($package->type) { case 'rpm': $xray = new RpmXray($this->download_repo_protocol, $this->download_repo_host, $_uri); break; case 'deb': $xray = new DebXray($this->download_repo_protocol, $this->download_repo_host, $_uri, $this->config['wget'], $this->config['wget_options'], $this->debug); break; default: throw new RuntimeException("Unknown file extension: " . $package->type . "(should be rpm or deb)."); } } catch (RuntimeException $e) { $this->log(' [EXCEPTION] ' . $e->getMessage()); // if there was a problem during xray (with code 999) // then it almost certainly means that the package no longer exists in the repository // so if the package exists in our database then remove it if ($package->guid && $e->getCode() == 999) { // if package deletion is OK then return immediately $result = $this->deletePackage($package, $project_name); } } if (is_object($xray)) { $package->license = $this->getLicense($xray->license, ''); $package->homepageurl = $xray->url; $package->category = $this->getCategory($xray->group); if (isset($xray->title) && strlen($xray->title)) { $package->title = $xray->title; } } // call the parent if ($package->guid) { $this->log(' update: ' . $package->filename . ' (title: ' . $package->title . ', guid: ' . $package->guid . ')'); $package->metadata->hidden = false; $package->update(); } else { if ($package->create()) { $this->log(' create: ' . $package->filename . ' (title: ' . $package->title . ', guid: ' . $package->guid . ')'); } else { $this->log(' failed to create: ' . $package->filename . ' (title: ' . $package->title . ')'); } } try { // if attachment creation failed then use the original OBS link $package->installfileurl = $this->api->getInstallFileURL($project_name, $repo_name, $arch_name, $package_name, $file_name); // get the file and store it locally // $fp might be a stream, or string if wget is in use $fp = $this->api->http->get_as_stream($this->api->getRelativeInstallPath($project_name, $repo_name, $arch_name, $package_name, $file_name)); if ($fp) { $attachment = $package->create_attachment($package_name . "_install.ymp", $package_name . "_install.ymp", "text/x-suse-ymp"); if ($attachment) { $blob = new midgard_blob($attachment); $handler = $blob->get_handler('wb'); if ($handler) { if (!$this->config['wget']) { $ymp = stream_get_contents($fp); } else { $ymp = $fp; } $origymp = $ymp; // replace name with the package name $ymp = self::replace_name_with_packagename($ymp, $package->name); if (!strlen($ymp)) { $this->log(' attempt to update package name in: ' . $attachment->name . ' would result in 0 byte long file; rollback, location: blobs/' . $attachment->location); $ymp = $origymp; } // write the attachment to the file system fwrite($handler, $ymp); if (!$this->config['wget']) { fclose($fp); } // close the attachment's handler fclose($handler); $attachment->update(); $this->log(' attachment created: ' . $attachment->name . ' (location: blobs/' . $attachment->location . ')'); } else { $this->log('Could not create attachment'); } } else { // could not create attachment, maybe we have it already $attachments = $package->list_attachments(); foreach ($attachments as $attachment) { if ($attachment->name == $package_name . "_install.ymp") { $blob = new midgard_blob($attachment); $handler = $blob->get_handler('rb+'); if ($handler) { $content = $blob->read_content(); $ymp = $content; if (strlen($content)) { $ymp = self::replace_name_with_packagename($content, $package->name); } if (!strlen($ymp)) { $this->log(' attempt to update package name in: ' . $attachment->name . ' would result in 0 byte long file; rollback, location: blobs/' . $attachment->location); $ymp = $content; } fwrite($handler, $ymp); fclose($handler); } else { $this->log(' failed to update attachment: ' . $attachment->name . ', location: blobs/' . $attachment->location); } break; } } } // set the install url field to the local attachment if (is_object($attachment)) { // write a local relative URL for the install file $package->installfileurl = '/mgd:attachment/' . $attachment->guid . '/' . $attachment->name; } // update because of the installfileurl stuff $package->update(); } } catch (RuntimeException $e) { $this->log(' [EXCEPTION] ' . $e->getMessage()); } // get the roles and create the necessary role objects $roles = $this->api->getPackageMeta($project_name, $package_name); foreach ($roles as $role => $userids) { foreach ($userids as $userid) { $this->log(' create role: ' . $userid . ' = ' . $role . ' (' . $package->guid . ')'); $this->createRole($package->guid, $userid, $role); } } // add relations by calling the parent class $this->addRelations($extinfo, $package); } return $package; }
/** * Goes through a release specified by a URL that points to a Release file * * @param string OBS project name, e.g. home:feri * @param string optional; specify a concrete package to be imported * @param boolean optional; if true then only cleanup will be performed on the local database * otherwise full import happens * */ public function go($release_file_url = null, $specific_package_name = null, $cleanonly = false) { // get release information from the relase file switch ($this->protocol) { case 'http': case 'https': $content = $this->http->get($release_file_url); break; case 'file': # todo break; } $release = $this->parseReleaseFile($content); if (!array_key_exists('Suite', $release)) { throw new RuntimeException('The Release files does not specify a suite.' . $packages_file_url); return null; } // generate name and tile from release information $project_name = strtolower($release['Origin'] . '-' . $release['Suite'] . '-' . $release['Label']); $project_title = ucwords($release['Origin'] . ' ' . $release['Suite'] . ' ' . $release['Label']); // check if the project is already recorded in our database // this returns a com_meego_project object $project = $this->getProject($project_name); // set properties $project->name = $project_name; $project->title = $project_title; $project->description = "Debian repository from " . ucfirst($release['Origin']); if (!$cleanonly) { if ($project->guid) { $log = 'Update project record: ' . $project->name; $project->update(); } else { $log = 'Create project record: ' . $project->name; $project->create(); } $this->log($log . ' (' . $project->title . ', ' . $project->description . ')'); } if ($project->id) { $this->log("\nComponents in {$project->name}:"); // get all components (ie repositories in the database) this Suite contains $repositories = explode(' ', $release['Components']); // iterate through each and every published repository // and dig out the packages foreach ($repositories as $repo_name) { $this->log("\n -> " . $repo_name); $architectures = explode(' ', $release['Architectures']); // get all available architectures within this repository foreach ($architectures as $arch_name) { $this->log("\n -> " . $arch_name); // get a com_meego_repository object $repo = $this->getRepository($repo_name, $arch_name, $project->id); $repo_title = ucfirst($release['Suite']) . ' ' . ucfirst($release['Label']) . ' ' . ucfirst($repo_name) . ' (for ' . $arch_name . ')'; // fill in properties of the repo object $repo->name = strtolower($release['Suite'] . '_' . $release['Label'] . '_' . $repo_name); $repo->title = $repo_title; $repo->arch = $arch_name; $repo->project = $project->id; $repo->os = $release['Origin']; $repo->osversion = $release['Suite']; $repo->osgroup = $release['Label']; $repo->osux = $release['ux']; if (!$cleanonly) { if ($repo->guid) { $log = ' update: '; $repo->update(); } else { $log = ' create: '; $repo->create(); } $this->log($log . $repo->name . ' (id: ' . $repo->id . '; ' . $repo->os . ' ' . $repo->osversion . ', ' . $repo->osgroup . ', ' . $repo->osux . ')'); } $fulllist = array(); // determine the url of the binary Packages file $packages_file_url = preg_replace('|Release$|', '', $release_file_url) . '/' . $repo_name . '/binary-' . $arch_name . '/Packages'; // get an array of all available packages // this array is a special one, so study the method for details $packages = $this->parsePackagesFile($packages_file_url); if (!count($packages)) { continue; } foreach ($packages as $package_name => $versions) { // iterate through each versions foreach ($versions as $package_version => $details) { $this->log("\n -> package #" . ++$this->package_counter . ': ' . $package_name . ' ' . $package_version); if ($cleanonly) { // only cleanup is requested so we can go to the next package continue; } // creates or updates a package in the database $package = $this->createPackage($project->name, $repo->id, $repo_name, $arch_name, $details); if (!$package) { // we got no package object, usually because we had to delete // an existing one from database, so go to next binary package continue; } $fulllist[] = $package->filename; try { $image_names = array_filter($this->api->getPackageSourceFiles($project->name, $package_name), function ($name) { $retval = false; $_icon_marker = 'icon.png'; $_screenshot_marker = 'screenshot.png'; if (strpos($name, $_icon_marker) === strlen($name) - strlen($_icon_marker) || strpos($name, $_screenshot_marker) === strlen($name) - strlen($_screenshot_marker)) { $retval = true; } return $retval; }); } catch (RuntimeException $e) { $this->log(' [EXCEPTION] ' . $e->getMessage()); } foreach ($image_names as $name) { try { $fp = $this->api->getPackageSourceFile($project->name, $package_name, $name); } catch (RuntimeException $e) { $this->log(' [EXCEPTION] ' . $e->getMessage()); } if ($fp) { $attachment = $package->create_attachment($name, $name, "image/png"); if ($attachment) { $blob = new midgard_blob($attachment); $handler = $blob->get_handler('wb'); fwrite($handler, stream_get_contents($fp)); fclose($fp); fclose($handler); $attachment->update(); } } } } } // now cleanup all the packages from our database // that are not part of this OBS repository $this->cleanPackages($repo, $fulllist); } } } // if $project->id }