[ Index ] |
PHP Cross Reference of GlotPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Things: GP_Project class 4 * 5 * @package GlotPress 6 * @subpackage Things 7 * @since 1.0.0 8 */ 9 10 /** 11 * Core class used to implement the projects. 12 * 13 * @since 1.0.0 14 */ 15 class GP_Project extends GP_Thing { 16 17 var $table_basename = 'gp_projects'; 18 var $field_names = array( 'id', 'name', 'slug', 'path', 'description', 'parent_project_id', 'source_url_template', 'active' ); 19 var $int_fields = array( 'id', 'parent_project_id', 'active' ); 20 var $non_updatable_attributes = array( 'id' ); 21 22 public $id; 23 public $name; 24 public $slug; 25 public $path; 26 public $description; 27 public $parent_project_id; 28 public $source_url_template; 29 public $active; 30 public $user_source_url_template; 31 32 /** 33 * Sets restriction rules for fields. 34 * 35 * @since 1.0.0 36 * 37 * @param GP_Validation_Rules $rules The validation rules instance. 38 */ 39 public function restrict_fields( $rules ) { 40 $rules->name_should_not_be( 'empty' ); 41 $rules->slug_should_not_be( 'empty' ); 42 } 43 44 // Additional queries 45 46 public function by_path( $path ) { 47 /** 48 * Filters the prefix for the locale glossary path. 49 * 50 * @since 2.3.1 51 * 52 * @param string $$locale_glossary_path_prefix Prefix for the locale glossary path. 53 */ 54 $locale_glossary_path_prefix = apply_filters( 'gp_locale_glossary_path_prefix', '/languages' ); 55 56 if ( $locale_glossary_path_prefix === $path ) { 57 return GP::$glossary->get_locale_glossary_project(); 58 } 59 60 $path = rawurlencode( urldecode( $path ) ); 61 $path = str_replace( '%2F', '/', $path ); 62 $path = str_replace( '%20', ' ', $path ); 63 $path = trim( $path, '/' ); 64 65 return $this->one( "SELECT * FROM $this->table WHERE path = %s", $path ); 66 } 67 68 /** 69 * Fetches the project by ID or object. 70 * 71 * @since 2.3.0 72 * 73 * @param int|object $thing_or_id A project or the ID. 74 * @return GP_Project|false The project on success or false on failure. 75 */ 76 public function get( $thing_or_id ) { 77 if ( is_numeric( $thing_or_id ) && 0 === (int) $thing_or_id ) { 78 return GP::$glossary->get_locale_glossary_project(); 79 } 80 81 return parent::get( $thing_or_id ); 82 } 83 84 /** 85 * Retrieves the sub projects 86 * 87 * @return array Array of GP_Project 88 */ 89 public function sub_projects() { 90 $sub_projects = $this->many( "SELECT * FROM $this->table WHERE parent_project_id = %d ORDER BY active DESC, id ASC", $this->id ); 91 92 /** 93 * Filter the list of sub-projects of a project. 94 * 95 * @since 1.0.0 96 * 97 * @param array $sub_projects An array of sub projects as GP_Project. 98 * @param string $project_id ID of the current project. Can be zero at the top level. 99 */ 100 $sub_projects = apply_filters( 'gp_projects', $sub_projects, $this->id ); 101 102 return $sub_projects; 103 } 104 105 public function top_level() { 106 $projects = $this->many( "SELECT * FROM $this->table WHERE parent_project_id IS NULL OR parent_project_id < 1 ORDER BY name ASC" ); 107 108 /** This filter is documented in gp-includes/things/project.php */ 109 $projects = apply_filters( 'gp_projects', $projects, 0 ); 110 111 return $projects; 112 } 113 114 // Triggers 115 116 /** 117 * Executes after creating a project. 118 * 119 * @since 1.0.0 120 * 121 * @return bool 122 */ 123 public function after_create() { 124 /** 125 * Fires after creating a project. 126 * 127 * @since 1.0.0 128 * 129 * @param GP_Project $project The project that was created. 130 */ 131 do_action( 'gp_project_created', $this ); 132 133 // TODO: pass some args to pre/after_create? 134 if ( is_null( $this->update_path() ) ) { 135 return false; 136 } 137 138 return true; 139 } 140 141 /** 142 * Executes after saving a project. 143 * 144 * @since 1.0.0 145 * @since 3.0.0 Added the `$project_before` parameter. 146 * 147 * @param GP_Project $project_before Project before the update. 148 * @return bool 149 */ 150 public function after_save( $project_before ) { 151 /** 152 * Fires after saving a project. 153 * 154 * @since 1.0.0 155 * @since 3.0.0 Added the `$project_before` parameter. 156 * 157 * @param GP_Project $project Project following the update. 158 * @param GP_Project $project_before Project before the update. 159 */ 160 do_action( 'gp_project_saved', $this, $project_before ); 161 162 // TODO: pass the update args to after/pre_save? 163 // TODO: only call it if the slug or parent project were changed 164 return ! is_null( $this->update_path() ); 165 } 166 167 /** 168 * Executes after deleting a project. 169 * 170 * @since 2.0.0 171 * 172 * @return bool 173 */ 174 public function after_delete() { 175 /** 176 * Fires after deleting a project. 177 * 178 * @since 2.0.0 179 * 180 * @param GP_Project $project The project that was deleted. 181 */ 182 do_action( 'gp_project_deleted', $this ); 183 184 return true; 185 } 186 187 /** 188 * Normalizes an array with key-value pairs representing 189 * a GP_Project object. 190 * 191 * @since 1.0.0 192 * 193 * @param array $args Arguments for a GP_Project object. 194 * @return array Normalized arguments for a GP_Project object. 195 */ 196 public function normalize_fields( $args ) { 197 if ( isset( $args['parent_project_id'] ) ) { 198 $args['parent_project_id'] = $this->force_false_to_null( $args['parent_project_id'] ); 199 } 200 201 if ( isset( $args['slug'] ) && ! $args['slug'] ) { 202 $args['slug'] = $args['name']; 203 } 204 205 if ( ! empty( $args['slug'] ) ) { 206 $args['slug'] = gp_sanitize_slug( $args['slug'] ); 207 } 208 209 if ( ( isset( $args['path'] ) && ! $args['path'] ) || ! isset( $args['path'] ) || is_null( $args['path'] ) ) { 210 unset( $args['path'] ); 211 } 212 213 if ( isset( $args['active'] ) ) { 214 if ( 'on' === $args['active'] ) { 215 $args['active'] = 1; 216 } 217 218 if ( ! $args['active'] ) { 219 $args['active'] = 0; 220 } 221 } 222 223 $args = parent::normalize_fields( $args ); 224 225 return $args; 226 } 227 228 // Helpers 229 230 /** 231 * Updates this project's and its children's paths, according to its current slug. 232 */ 233 public function update_path() { 234 global $wpdb; 235 $old_path = isset( $this->path ) ? $this->path : ''; 236 $parent_project = $this->get( $this->parent_project_id ); 237 if ( $parent_project ) { 238 $path = gp_url_join( $parent_project->path, $this->slug ); 239 } elseif ( ! $wpdb->last_error ) { 240 $path = $this->slug; 241 } else { 242 return null; 243 } 244 245 $path = trim( $path, '/' ); 246 247 $this->path = $path; 248 $res_self = $this->update( array( 'path' => $path ) ); 249 if ( is_null( $res_self ) ) { 250 return $res_self; 251 } 252 // Update children's paths, too. 253 if ( $old_path ) { 254 $query = "UPDATE $this->table SET path = CONCAT(%s, SUBSTRING(path, %d)) WHERE path LIKE %s"; 255 return $this->query( $query, $path, strlen( $old_path ) + 1, $wpdb->esc_like( $old_path ) . '%' ); 256 } else { 257 return $res_self; 258 } 259 } 260 261 /** 262 * Regenerate the paths of all projects from its parents slugs 263 */ 264 public function regenerate_paths( $parent_project_id = null ) { 265 // TODO: do it with one query. Use the tree generation code from GP_Route_Main::_options_from_projects() 266 if ( $parent_project_id ) { 267 $parent_project = $this->get( $parent_project_id ); 268 $path = $parent_project->path; 269 } else { 270 $path = ''; 271 $parent_project_id = null; 272 } 273 274 $projects = $this->find( array( 'parent_project_id' => $parent_project_id ) ); 275 foreach ( (array) $projects as $project ) { 276 $project->update( array( 'path' => trim( gp_url_join( $path, $project->slug ), '/' ) ) ); 277 $this->regenerate_paths( $project->id ); 278 } 279 } 280 281 public function source_url( $file, $line ) { 282 $source_url = false; 283 if ( $source_url_template = $this->source_url_template() ) { 284 $source_url = str_replace( array( '%file%', '%line%' ), array( $file, $line ), $source_url_template ); 285 } 286 287 /** 288 * Allows per-reference overriding of the source URL defined as project setting. 289 * 290 * @since 2.2.0 291 * 292 * @param string|false $source_url The originally generated source URL, or false if no URL is available. 293 * @param GP_Project $project The current project. 294 * @param string $file The referenced file name. 295 * @param string $line The line number in the referenced file. 296 */ 297 return apply_filters( 'gp_reference_source_url', $source_url, $this, $file, $line ); 298 } 299 300 public function source_url_template() { 301 if ( isset( $this->user_source_url_template ) ) { 302 return $this->user_source_url_template; 303 } else { 304 if ( $this->id && is_user_logged_in() && ( $templates = get_user_meta( get_current_user_id(), 'gp_source_url_templates', true ) ) 305 && isset( $templates[ $this->id ] ) ) { 306 $this->user_source_url_template = $templates[ $this->id ]; 307 return $this->user_source_url_template; 308 } else { 309 return $this->source_url_template; 310 } 311 } 312 } 313 314 /** 315 * Gives an array of project objects starting from the current project 316 * then its parent, its parent and up to the root. 317 * 318 * @todo Cache the results. Invalidation is tricky, because on each project update we need to invalidate the cache 319 * for all of its children. 320 * 321 * @return array 322 */ 323 public function path_to_root() { 324 $path = array(); 325 if ( $this->parent_project_id ) { 326 $parent_project = $this->get( $this->parent_project_id ); 327 328 if ( $parent_project ) { 329 $path = $parent_project->path_to_root(); 330 } 331 } 332 return array_merge( array( &$this ), $path ); 333 } 334 335 public function set_difference_from( $other_project ) { 336 $this_sets = (array) GP::$translation_set->by_project_id( $this->id ); 337 $other_sets = (array) GP::$translation_set->by_project_id( $other_project->id ); 338 $added = array(); 339 $removed = array(); 340 341 foreach ( $other_sets as $other_set ) { 342 if ( ! gp_array_any( array( $this, '_compare_set_item' ), $this_sets, $other_set ) ) { 343 $added[] = $other_set; 344 } 345 } 346 347 foreach ( $this_sets as $this_set ) { 348 if ( ! gp_array_any( array( $this, '_compare_set_item' ), $other_sets, $this_set ) ) { 349 $removed[] = $this_set; 350 } 351 } 352 353 return array( 354 'added' => $added, 355 'removed' => $removed, 356 ); 357 } 358 359 public function _compare_set_item( $set, $this_set ) { 360 return ( $set->locale == $this_set->locale && $set->slug = $this_set->slug ); 361 } 362 363 public function copy_sets_and_translations_from( $source_project_id ) { 364 $sets = GP::$translation_set->by_project_id( $source_project_id ); 365 366 foreach ( $sets as $to_add ) { 367 $new_set = GP::$translation_set->create( 368 array( 369 'project_id' => $this->id, 370 'name' => $to_add->name, 371 'locale' => $to_add->locale, 372 'slug' => $to_add->slug, 373 ) 374 ); 375 if ( ! $new_set ) { 376 $this->errors[] = sprintf( 377 /* translators: %s: Translation set. */ 378 __( 'Couldn’t add translation set named %s', 'glotpress' ), 379 esc_html( $to_add->name ) 380 ); 381 } else { 382 // Duplicate translations. 383 $new_set->copy_translations_from( $to_add->id ); 384 } 385 } 386 } 387 388 public function copy_originals_from( $source_project_id ) { 389 global $wpdb; 390 return $this->query( 391 "INSERT INTO $wpdb->gp_originals ( 392 `project_id`, `context`, `singular`, `plural`, `references`, `comment`, `status`, `priority`, `date_added` 393 ) 394 SELECT 395 %s AS `project_id`, `context`, `singular`, `plural`, `references`, `comment`, `status`, `priority`, `date_added` 396 FROM $wpdb->gp_originals WHERE project_id = %s", 397 $this->id, 398 $source_project_id 399 ); 400 } 401 402 /** 403 * Gives an array of project objects starting from the current project children 404 * then its grand children etc. 405 * 406 * @return array 407 */ 408 public function inclusive_sub_projects() { 409 $sub_projects = $this->sub_projects(); 410 foreach ( $sub_projects as $sub ) { 411 $sub_projects = array_merge( $sub_projects, $sub->inclusive_sub_projects() ); 412 } 413 414 return $sub_projects; 415 } 416 417 public function duplicate_project_contents_from( $source_project ) { 418 $source_sub_projects = $source_project->inclusive_sub_projects(); 419 420 // Duplicate originals, translations sets and translations for the root project. 421 $this->copy_originals_from( $source_project->id ); 422 $this->copy_sets_and_translations_from( $source_project->id ); 423 424 // Keep a list of parents to preserve hierarchy. 425 $parents = array(); 426 $parents[ $source_project->id ] = $this->id; 427 428 // Duplicate originals, translations sets and translations for the child projects. 429 foreach ( $source_sub_projects as $sub ) { 430 $copy_project = new GP_Project( $sub->fields() ); 431 $copy_project->parent_project_id = $parents[ $sub->parent_project_id ]; 432 $parent_project = $copy_project->get( $copy_project->parent_project_id ); 433 434 $copy_project->path = gp_url_join( $parent_project->path, $copy_project->slug ); 435 $copy = GP::$project->create( $copy_project ); 436 $parents[ $sub->id ] = $copy->id; 437 438 $copy->copy_originals_from( $sub->id ); 439 $copy->copy_sets_and_translations_from( $sub->id ); 440 } 441 } 442 443 /** 444 * Deletes a project and all of sub projects, translations, translation sets, originals and glossaries. 445 * 446 * @since 2.0.0 447 * 448 * @return bool 449 */ 450 public function delete() { 451 GP::$project->delete_many( array( 'parent_project_id' => $this->id ) ); 452 453 GP::$translation_set->delete_many( array( 'project_id' => $this->id ) ); 454 455 GP::$original->delete_many( array( 'project_id' => $this->id ) ); 456 457 return parent::delete(); 458 } 459 } 460 GP::$project = new GP_Project();
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Dec 3 01:01:05 2024 | Cross-referenced by PHPXref 0.7.1 |